下笔春蚕食叶声。

【Topcoder 12004】SetAndSet 容斥+并查集

题意

Topcoder 12004
把一堆数分成两堆,要求这两堆各自的按位与和相等,求分的方案数。
\((n \le 50, 0 \le A_i < 2^ {20})\)

题解

注意到这个 \(A_i\) 很小, \(n\) 也很小,看起来适合状压、容斥。
考虑容斥各二进制位,如果一个位置被钦定不合法,那么这位上所有的\(0\)都属于同一个联通块(一个位置合法,当且仅当所有数该位都是1或者两堆各自至少有一个0)
并查集维护,最后统计。
不开long long见祖宗!

代码

code
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
typedef long long ll;
const int N = 55, lim = 21;
class SetAndSet{
	public: 
	int n, fa[lim][N], cnt[lim], id[lim][N]; ll ans;
	int find(int pos, int x){
		return fa[pos][x] = ((x == fa[pos][x]) ? x : find(pos, fa[pos][x]));
	}
	bool merge(int pos, int x, int y){
		int fx = find(pos, x), fy = find(pos, y);
		if(fx == fy) return 0;
		fa[pos][fx] = fy; return 1;
	}
	void dfs(int pos, int num, int op){
		if(pos == lim)
			return ans += op * ((1ll << num) - 2), void();
		if(pos == 0){
			for(int i = 0; i < n; i++) fa[pos][i] = i;
		} else {
			for(int i = 0; i < n; i++) fa[pos][i] = fa[pos - 1][i];
		}
		dfs(pos + 1, num, op);
		if(cnt[pos]){
			for(int i = 2; i <= cnt[pos]; i++)
				if(merge(pos, id[pos][i], id[pos][1])) num--;
			dfs(pos + 1, num, -op); 
		} 
	}
	ll countandset(vector<int> A){
		n = A.size(); ans = 0;
		for(int i = 0; i < lim; i++){
			cnt[i] = 0;
			for(int j = 0; j < n; j++)
				if(!((A[j] >> i) & 1))
					id[i][++cnt[i]] = j;
		} 
		dfs(0, n, 1);
		return ans;
	}
};
/*
O(2^20*nlogn)
十年OI一场空,没开longlong见祖宗
*/
posted @ 2021-06-27 11:15  ACwisher  阅读(97)  评论(1编辑  收藏  举报