我有一组项目,例如:{1,1,1,2,2,3,3,3}和一组限制,例如{{3},{1,2} ,{1,2,3},{1,2,3},{1,2,3},{1,2,3},{2,3},{2,3}。我正在寻找项目的排列,但第一个元素必须是3,第二个元素必须是1或2,等等。
一种适合的排列是: {3,1,1,1,2,2,3}
是否有算法可以统计此问题的所有排列?这类问题有名称吗?
为了说明,我知道如何为某些类型的“限制集”解决这个问题。 项目集:{1,1,2,2,3},限制{{1,2},{1,2,3},{1,2,3},{1,2},{1,2 }}。这相当于2!/(2-1)!/ 1! * 4!/ 2!/ 2!。首先有效地置换3,因为它是最具限制性的,然后置换有空间的剩余物品。
也是...多项式时间。这可能吗?
更新:这将在以下链接中进一步讨论。上面的问题被称为“计算完美匹配”,上面的每个排列限制都由占用者的矩阵矩阵上的{0,1}表示。
答案 0 :(得分:1)
您可能会考虑使用数字池的递归解决方案(在您提供的示例中,它将初始化为{1,1,1,2,2,3,3,3}),并决定,作为参数给出的索引,该数字放在该索引处(当然使用您提供的限制)。
如果您愿意,我可以提供伪代码。
答案 1 :(得分:1)
你可以建造一棵树。 级别0:创建根节点。 级别1:将第一个“限制集”中的每个项目作为根的子项附加。 级别2:将第二个限制集中的每个项目作为每个1级节点的子级附加。 级别3:将第三个限制集中的每个项目作为每个级别2节点的子级附加。 ...
排列计数是最终树的叶节点数。
修改强>
目前还不清楚“项目集”{1,1,1,2,2,3,3,3}是什么意思。如果这意味着约束每个值可以使用多少次(“1”可以使用3次,“2”可以使用两次,等等),那么我们还需要一个步骤:
答案 2 :(得分:1)
这里所有其他解决方案都是指数时间 - 即使是他们不需要的情况。这个问题表现出类似的子结构,因此应该用动态规划来解决。
你想要做的是写一个记住子问题解决方案的类:
class Counter {
struct Problem {
unordered_multiset<int> s;
vector<unordered_set<int>> v;
};
int Count(Problem const& p) {
if (m.v.size() == 0)
return 1;
if (m.find(p) != m.end())
return m[p];
// otherwise, attack the problem choosing either choosing an index 'i' (notes below)
// or a number 'n'. This code only illustrates choosing an index 'i'.
Problem smaller_p = p;
smaller_p.v.erase(v.begin() + i);
int retval = 0;
for (auto it = p.s.begin(); it != p.s.end(); ++it) {
if (smaller_p.s.find(*it) == smaller_p.s.end())
continue;
smaller_p.s.erase(*it);
retval += Count(smaller_p);
smaller_p.s.insert(*it);
}
m[p] = retval;
return retval;
}
unordered_map<Problem, int> m;
};
代码说明了选择一个索引i,它应该在v [i] .size()很小的地方选择。另一个选择是选择一个数字n,它应该是一个可以放置的位置v很少的数字。我会说两个决定因素中的最小值应该会赢。
另外,你必须为问题定义一个哈希函数 - 使用boost的哈希函数不应该太难。
该解决方案可以通过用集合&替换矢量并且定义&lt; unordered_set的运算符。这会将更多相同的子问题折叠成一个地图元素,并进一步减少指数的爆发。
通过使问题实例相同,除了将数字重新排列为相同的值并进行比较之外,可以进一步改进此解决方案。
答案 3 :(得分:0)
为了节省空间,您可以构建有向图而不是树。
然后,排列的数量是从根节点到最终集节点的路径数。