1. 程式人生 > 實用技巧 >一道容斥神仙題

一道容斥神仙題

題意:

簡略題意:

n <= 1e6,0 <= A[i] <= 1e6

首先有一個很顯然的n * A[i]的子集DP,這裡就不再贅述

這題的難點在於把數字看成集合,然後求若干個集合並起來為空的方案數,並起來為空很不想,我們可以考慮並起來恰好有0個元素,"恰好"是不是有二次項反演那感覺了?可惜這題限制條件太多,不好反演或者反演不了

但這給我們啟發,畢竟一切二次項反演題基本都可以用容斥來做,我們可以考慮用容斥來做,加上並起來恰好為0個元素的,減去恰好為1個元素的......所以我們現在要求有多少個集合並起來至少有i個元素,轉化為數字即(bit(x) == i)

(bit[x]即x的二進位制下有幾個一),我們可以先欽定有x個元素,即列舉x的超集,設為dp[x],從數字上也可以理解為(滿足A[i] & x == A[i])的i有多少個,這個顯然可以列舉子集暴力DP,然後每個x的超集可以可選可不選,但不能全不選(全不選並完就變成了空集)

所以對於單個數x貢獻是2^dp[x] - 1;然後容斥一下即可(注意在集合和數字間轉化,個人覺得是本題最大難點)

程式碼如下:

/*陰影之內*/
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
int read(){
	char c = getchar();
	int x = 0;
	while(c < '0' || c > '9')	c = getchar() ;
	while(c >= '0' && c <= '9')		x = x * 10 + c - 48,c = getchar() ;
	return x;
} 
const int maxn = 1e6 + 10;
const int mod = 1e9 + 7;
int dp[maxn];
int mi[maxn];
void init(){
	mi[0] = 1;
	for(int i = 1; i <= maxn - 10; ++i)		mi[i] = 2ll * mi[i-1] % mod;
}
int main(){
	int n = read();
	init();
	for(int i = 1; i <= n; ++i){
		int x = read();dp[x]++;
	}
	for(int i = 0; i <= 20; ++i)
		for(int j = 0; j <= maxn - 10; ++j)
			if(j & (1 << i))
				dp[j^(1<<i)] += dp[j];
	ll Ans = 0;
	for(int i = 0; i <= maxn - 10; ++i){
		int num = 0;
		for(int j = 0; j <= 20; ++j)
			if(i & (1 << j))	num++;
		if(num & 1) 	Ans = (Ans - (mi[dp[i]] - 1) + mod) % mod;
		else	Ans = (Ans + (mi[dp[i]] - 1)) % mod;
	}
	cout<<Ans<<endl;
	return 0;
}