链接:http://codeforces.com/contest/1290/problem/C
n(3e5)个灯,开始有亮有灭,k(3e5)个集合,每次操作可以改变某个集合内所有灯的状态。保证任意三个集合之交为空,问使1、12、123、…、1-n这n种组合的灯同时亮分别至少需要多少次操作(题目保证存在一种操作方案使所有灯同时亮)
三个集合交为空的条件易转化为每个灯最多只包含在两个集合中。考虑把集合作为点,灯作为边,用点的二染色代表是否操作某个集合,白代表选、黑代表不选。则原来亮的灯表示边的两端点同色,黑的边表示两端点异色。由于题目保证存在合法染色,只需要用并查集维护一下就可了
比赛的时候看到异或、亮灭两个元素就死命想2-sat了。。。遂卒。那么问题来了,2-sat资不资持动态加边?
#include <stdio.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
int n,k,a[302020],m,aa,b[303030],size[303003],sum[303030],f[303030],cho[303030],ans = 0,con[303030],col[303030],s[303030];
char S[303030];
bool fix[303030];
inline int getnum(int x) {
if (fix[x])
return con[x]?size[x]-sum[x]:sum[x];
return min(sum[x],size[x]-sum[x]);
}
inline int getf(int x) {
if (f[x] == x)
return x;
int tmp = getf(f[x]);
col[x] = col[x]^col[f[x]];
f[x] = tmp;
return f[x];
}
int main() {
scanf("%d%d",&n,&k);
scanf("%s",S+1);
for (int i = 1;i <= n; i++)
s[i] = S[i]-'0';
for (int i = 1;i <= k; i++) {
f[i] = i;
size[i] = sum[i] = 1;
cho[i] = 1;
col[i] = 0;
scanf("%d",&m);
for (int j = 1;j <= m; j++) {
scanf("%d",&aa);
if (!a[aa])
a[aa] = i;
else
b[aa] = i;
}
}
for (int i = 1;i <= n; i++) {
if (!b[i]) {
int fa = getf(a[i]);
ans -= getnum(fa);
if (s[i])
con[fa] = col[a[i]]^1;
else
con[fa] = col[a[i]];
fix[fa] = 1;
ans += getnum(fa);
}
else if (a[i] && b[i]) {
int fa = getf(a[i]),fb = getf(b[i]);
if (fa != fb) {
ans -= getnum(fa);
ans -= getnum(fb);
size[fb] += size[fa];
f[fa] = fb;
col[fa] = col[a[i]]^col[b[i]]^s[i]^1;
if (fix[fa])
con[fb] = col[fa]^con[fa],fix[fb] = 1;
if (col[fa])
sum[fb] += size[fa]-sum[fa];
else
sum[fb] += sum[fa];
ans += getnum(fb);
}
}
printf("%d\n",ans);
}
return 0;
}