CF1290C Prefix Enlightenment (并查集)

大意: 给定长$n$的01串$s$, 给定$k$个集合$A_1,...,A_k$,保证任意三个集合交集为空. 每次操作选择一个集合,翻转$s$中对应位置. 定义$m_i$为使前$i$个位置全为$1$所需的最少操作数(题目数据保证每个$m_i$都存在), 求所有$m_i$的值.

 

 

显然每个位置最多属于两个集合.

假设只对应一个集合$X$, 那么$X=\overline{s_i}$

假设对应集合$X,Y$, 那么$X\oplus Y=\overline{s_i}$

所以题目就等价于给定一些异或方程组, 求最少多少个变量值为1.

考虑把每个集合拆成两个点$X_1,X_0$表示选或不选. 

对于$X\oplus Y=1$, 就连边$(X_1,Y_0),(X_0,Y_1)$

对于$X\oplus Y=0$, 就连边$(X_1,Y_1),(X_0,Y_0)$

对于$X=\overline{s_i}$, 有一个技巧是设一个权值无穷大的点, 限制$X$不能选$s_i$.

可以用并查集实现, 选一个权值和最小的连通块即可. 

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#define REP(_i,_a,_n) for(int _i=_a;_i<=_n;++_i)
#define PER(_i,_a,_n) for(int _i=_n;_i>=_a;--_i)
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int P = 1e9+7, INF = 0x3f3f3f3f;
const int N = 1e6+10;
int n, k, fa[N], val[N];
char s[N];
vector<int> g[N];
int Find(int x) {return fa[x]?fa[x]=Find(fa[x]):x;}
void add(int x, int y) {
	x = Find(x), y = Find(y);
	if (x!=y) fa[x]=y,val[y]+=val[x];
}
int calc(int x) {
	return min(val[Find(x)],val[Find(x+k)]);
}

int main() {
	scanf("%d%d%s",&n,&k,s+1);
	REP(i,1,k) {
		int x, t;
		scanf("%d",&x);
		while (x--) {
			scanf("%d", &t);
			g[t].pb(i);
		}
	}
	REP(i,k+1,2*k) val[i] = 1;
	val[2*k+1] = INF;
	int ans = 0;
	REP(i,1,n) {
		if (g[i].size()==1) {
			ans -= calc(g[i][0]);
			if (s[i]=='1') add(g[i][0]+k,2*k+1);
			else add(g[i][0],2*k+1);
			ans += calc(g[i][0]);
		}
		else if (g[i].size()==2) {
			int x = g[i][0], y = g[i][1];
			if (Find(x)!=Find(y)&&Find(x)!=Find(y+k)) {
				ans -= calc(x)+calc(y);
				if (s[i]=='1') add(x,y),add(x+k,y+k);
				else add(x+k,y),add(x,y+k);
				ans += calc(x);
			}
		}
		printf("%d\n", ans);
	}
}

 

posted @ 2020-02-07 13:00  uid001  阅读(290)  评论(0编辑  收藏  举报