CF449D Jzzhu and Numbers題解
阿新 • • 發佈:2020-08-20
link
solve
我們發現直接去統計方案數不大現實,我們就考慮用容斥的做法,用全部的方案數-不為 \(0\) 的方案數,我們就可以定義 \(F[i]\) 表示 \(a[i]\&i==i\) 的個數,我們先按位列舉,然後從1-1e6列舉j如果j在這一位上有數,我們就可以把這一位上的數去掉作為上一狀態轉移過來,即 \(dp[j^(1<<i-1)]+=dp[j]\) 為什麼要講小的狀態加上大的狀態,因為防止刷重
怎樣來確定正確性,我們先刷了去掉第一位的,然後列舉到第二位時刷去掉前兩位的和去掉第二位的,列舉到第n位時刷了前面的組合和去掉第n位的,所以我們把所有狀態都刷到了,因此答案是正確的。
考慮答案,我們發現,有些答案被統計了兩次,比如F[101]在F[100]的時候統計了一次,在F[001]的時候也被統計了一次,所以在我們要把F[101]減去一次,就是容斥的原理,所以二進位制下1的個數為奇數就加上,為偶數就減去,最後注意模數就好了。
code
程式碼的陣列名稱和題解不同
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #define mod 1000000007 #define N 3000000 using namespace std; inline int read(){ int ret=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar(); return ret*f; } int n,x,dp[N],g[N]; long long f[N]; long long ans=0; int main() { scanf("%d",&n); int mx=(1<<20)-1; for(int i=1;i<=n;i++)dp[read()]++; f[0]=1; for(int i=1;i<=n;i++) f[i]=(f[i-1]<<1)%mod; for(int i=1;i<=20;i++) for(int j=0;j<=mx;j++) if(j&(1<<i-1)){ dp[j^(1<<i-1)]+=dp[j]; } ans=f[n]-1; for(int i=1;i <= mx;i++) { g[i]=g[i>>1]+(1&i); if(g[i]&1) ans=(ans-(f[dp[i]]-1)+mod)%mod; else ans=(ans+(f[dp[i]]-1)+mod)%mod; } printf("%lld\n",ans); return 0; }