[BZOJ4903][Ctsc2017]吉夫特
题目描述
题解:
因为奇*奇=奇,奇*偶=偶,偶*偶=偶,所以上面让求的其实就是找到一个序列,使得后面一个数和前面一个数的组合数是奇数。
那么根据$Lucas$定理,$\binom{n}{m}\ mod\ p=\binom{n\ mod\ p}{m\ mod\ p} \times \binom{\frac{n}{p}}{\frac{m}{p}}$。
如果$p$是2,那么$\binom{0}{0} = 1$, $\binom{1}{0}=1$,$\binom{0}{1} = 0$,$\binom{1}{1}=1$,可知使得$\binom{n}{m}\ mod\ 2 = 1$的必要条件是$n \& m = m$.
所以上面的条件可以转化为一个数列中后一项比前一项小,且后面那个数是前面的一个数的子集。
那我们设$f[i]$表示以$i$结尾的满足题意的序列的方案个数,那么我们对于每个$a[i]$枚举它的子集$j$,如果它出现过并且位置在$i$之后那么$f[j]+=f[a[i]]$。
这样同时保证了后面数的比前面的小并且后面的数是前面的数的子集。
枚举子集的好处就是可以优化时间复杂度,因为如果暴力枚举的话是$O(N^2)$的,但是枚举子集是$O(3^{log233333})$的。
这个优化方法很巧妙,如果以后做有关位运算方面的题的时候无法优化时间复杂度,可以试试用枚举子集。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define reg register inline int read() { int res = 0;char ch=getchar();bool fu=0; while(!isdigit(ch))fu|=(ch=='-'),ch=getchar(); while(isdigit(ch)) res=(res<<3)+(res<<1)+(ch^48),ch=getchar(); return fu?-res:res; } int n; int a[2500000]; int f[2500000], pos[2500000]; int ans; #define mod 1000000007 int main() { n = read(); for (reg int i = 1 ; i <= n ; i ++) a[i] = read(), f[a[i]] = 1, pos[a[i]] = i; for (reg int i = 1 ; i <= n ; i ++) for (reg int j = a[i] ; j ; j = (j - 1) & a[i]) if (a[i] != j and pos[j] > i) (f[j] += f[a[i]]) %= mod; for (reg int i = 1 ; i <= n ; i ++) (ans += f[a[i]] - 1) %= mod; cout << (ans + mod) % mod << endl; return 0; }