动态规划中的包含 - 排除

时间:2015-07-14 08:36:21

标签: algorithm math

有N名士兵(编号从1到N)。每个士兵都拥有M种不同技能的一些技能(编号从1到M)。军队的技能组合是其组成士兵的技能组合。有多少不同的士兵满足哪些具有特定的技能要求

Problem Link


根据解释问题减少到找到这些数字的子集数量,其OR正好等于所需的值,比如req



设f(i)为数j的数量,使得j OR i = i。

则答案为
Σi(-1)^ popcount(i xor req)(2 ^ f(i)-1)对于所有我来说i或req是req

请解释上面的公式及其如何实现

我知道选择N元素的方法有多少是(2 ^ n-1)但为什么这个术语(−1)^popcount(i xor req)

请解释算法。

2 个答案:

答案 0 :(得分:2)

这是inclusion exclusion

让我们来看看问题的简化版本。假设您有2套AB。假设您正在寻找具有正好1个元素的子集的大小。

首先,您计算|A| + |B|。但是在这里,您在| A [intersection] B||A|中计算了两次交叉点|B|的大小,因此您将其缩小并获得|A| + |B| - |A [intersection] B|

同样,对于3组,您首先要添加所有集的大小:|A| + |B| + |C|
然后,你减少所有"连接"在两组|A [intersection] B|, |A [intersection] C|, |B [intersection] C|之间。但现在,您删除了太多元素,还删除了|A [intersection] B [intersection] C|中的元素。要克服它,请添加此大小。

因此,概括它 - 您获得了n套和m所需的属性:

#of items with m properties: 
E(m) = sum { (-1)^(r-m) * Choose(r,m) * W(r) } 
Where: 
W(r) = summation of all intersections of size i, or formally:
W(r) = sum { W(p_k_1,...,p_k_r) | 1<=k_1<k_2<...<k_r<=n } 
W(pk1,...,pkr) = |X_k1 [intersection] X_k2 [intersection] .... [intersection] X_kr|

这解释了(-1)^x如何发挥作用,我们重复包含(添加)和排除(减少)尺寸。 (-1)^ x允许我们自动&#34;减少/添加求和中的每个元素。

正如你所说,popcount(i xor req)正在计算&#34; up&#34; i^req表示中的位。这意味着,popcount()实际上是上述公式中的索引i,您的集合为:X_i = all soldiers with required property i
这意味着如果k = popcount(i XOR req)i完全得到k&#34;必需&#34;属性,并且属于r=k时的值的上述总和,因此属性add / substract取决于(-1)^k = (-1)^popcount(i XOR req)的值

答案 1 :(得分:1)

在阅读社论并自己获得AC后,我不认为这里容易理解“包含 - 排除原则”的应用,这个问题围绕Codeforces Div.1问题C / D级别,例如,这一个:http://codeforces.com/contest/449/problem/D

重写版本@ 2016

  

以前的答案太乱了,我试着清理它并重写一个更易读的答案

我将解释为什么解决方案有效,但 如何提出这样的解决方案,我认为这更多是关于体验。

大图解释

首先,不要过度思考问题,给定a[1..N],解决方案实际上是所有可以产生req

的子集

话虽如此,我们怎样才能找到可以产生x的所有子集? 如解决方案所示,我们定义f(i)

  

f(i)ji = i的数字。

如果您觉得难以理解,请考虑其物理意义:

  

对于任何iji = i iff j可由带走一些二进制位1远离 i

例如,如果i = 7,那么j可以是0,1,2 ...,7;如果i = 5,那么j可以是0,1,4,5

因此,2^f(i) - 1确实是当OR i

时可以产生i的可能子集的数量

在这里等一下,确保先完成上述部分。

现在如果i = req本身怎么办?这是什么意思? 2^f(req)-1中计算了哪些子集? 2^f(req)-1如何与我们想要的答案相关?

  

我们想要OR子集的所有元素将产生req

     

2^f(req)-1给出了所有元素OR req将产生req

的子集数量

你能闻到2^f(req)-1可能比我们需要的更多吗?

这样考虑:有一些子集2^f(req)-1计数,或者所有元素只能生成x,其中x可以通过从{{1中取走一些二进制位1来生成req (见上面的方块引用)

所以,我们必须从2^f(req)-1中删除一些内容,然后是包含 - 排除原则

假设我们想要减去OR所有元素等于x的子集,即req带走一个二进制位1

有了类似的想法,你会发现必须减去2^f(x)-1,对于所有可能的x,在这里,请记住 x是所有数字{{1}拿掉一个二进制位1

但是,你会减去超过你应该的数量,因为不同的req可能会共享同一个x,其中yy带走两个二进制位1,或req y带走一个二进制位1,您可以从x中删除每个y一次,但是在减去2^f(req)-1的过程中,对于某些2^f(x)-1,您有多次减去!

通过包含 - 排除原则,您必须将它们添加回来......我们在这里有一点摘要:

  

我们想要的是y - 2^f(req)-1 + 2^f(x)-1 ...其中2^f(y)-1是一组数字等于x删除一个二进制位1, req是一组数字,等于y删除两个二进制位1(或req删除一个二进制位1)

你能看到这里的模式吗?是的,这是公式

中的x部分

对于所有-1^popcount()&lt; = i req的每一位都与i或0 相同,那么{{1等于req(删除i xor req的所有1位),所以req - i确实从i中删除了二进制位1的#为了得到popcount(i xor req)

综合整个故事,形成了公式:req适用于所有iSum (-1)^popcount(i xor req) * (2^f(i) - 1)

DP计算f(i)

i

以下是计算i OR req = req

的DP部分

请注意,它基于以下重现关系:

  

f(i)= for(int i=0;i<20;i++) for(int j=0; j<=(1<<20); j++) { if(j&(1<<i)) { f[j] += f[j^(1<<i)]; } }本身+ f(有些j是我删除了第1位)

     

因此,如果我的第j位为1,则f(i)= 1 + f(i xor(1 <&lt; j))

请注意,f(i)必须小于i,因此DP订单。