E. Prefix Enlightenment
http://codeforces.com/contest/1291/problem/E
题意:有一个01字符串s,长度为n,有k个集合
A
1
,
A
2
,
.
.
.
A
k
A_1,A_2,...A_k
A1,A2,...Ak ,他们都是{
1
,
2
,
.
.
.
,
n
1,2,...,n
1,2,...,n }的子集,选取集合
A
i
A_i
Ai 能将字符串s中对应下标的字符反转(1 - 0,0 - 1,如s = “1001”,
A
1
=
A_1 =
A1= {1,3},选取
A
1
A_1
A1能使得s变为"0011"),现问对于所有
i
∈
[
1
,
n
]
i \in [1,n]
i∈[1,n],最少应该选取多少个集合使得字符串s的前缀1 ~ i都变为为1
思路:首先题意保证每个位置被一或两个集合覆盖(如果某个位置为1可能没有集合覆盖),将集合 A i A_i Ai拆为两个点 x i , 1 , x i , 0 x_{i,1},x_{i,0} xi,1,xi,0分别表示这个集合选或不选,val[ x i , 0 x_{i,0} xi,0] = 0,val[ x i , 1 x_{i,1} xi,1] = 1
现考虑下标i有两个集合 A a , A b A_a,A_b Aa,Ab覆盖的情况,如果s[i] = 0,则a,b两个集合有且仅有一个被选取,即要么选择 x a , 1 和 x b , 0 x_{a,1}和x_{b,0} xa,1和xb,0,要么选择 x a , 0 和 x b , 1 x_{a,0}和x_{b,1} xa,0和xb,1,可以将 x a , 1 和 x b , 0 x_{a,1}和x_{b,0} xa,1和xb,0合并,同样 x a , 0 和 x b , 1 x_{a,0}和x_{b,1} xa,0和xb,1合并,因为下标i之前还有很多约束让某些点必须同时选择,所以实际上是将两个点所在集合合并,合并后得到两个新的集合,选取其中val较小的即可,s[i] = 1时便是a,b两个集合要么同时选择,要么都不选,操作同
如果下标i处只有一个集合a覆盖,如果s[i] = 0,则表示a必选,那么可以新建一个点B,val为无穷大,将 x a , 0 x_{a,0} xa,0连到B上面可以保证集合a必选,s[i] = 1类似
#include<bits/stdc++.h>
#define MAXN 300005
#define INF 0x3f3f3f3f
using namespace std;
int n,k,pre[MAXN*2];
char s[MAXN];
inline int find(int x)
{
return x == pre[x] ? x : pre[x] = find(pre[x]);
}
int val[MAXN*2],tmp,m;
vector<int> ve[MAXN];
int ans;
inline int getmin(int x)
{
return min(val[find(x)],val[find(x+k)]);
}
inline void merge(int x,int y)
{
int xx = find(x),yy = find(y);
if(xx != yy)
{
pre[xx] = yy;
val[yy] += val[xx];
}
}
int main()
{
scanf("%d%d%s",&n,&k,s+1);
for(int i = 1;i <= k;++i)
{
scanf("%d",&m);
while(m--)
{
scanf("%d",&tmp);
ve[tmp].push_back(i);
}
}
for(int i = 1;i <= k;++i)
{
pre[i] = i,val[i] = 1;
pre[i+k] = i+k,val[i+k] = 0;
}
pre[2*k+1] = 2*k+1,val[2*k+1] = INF;
ans = 0;
for(int i = 1;i <= n;++i)
{
if(s[i] == '0')
{
if(ve[i].size() == 1)
{
int id = ve[i][0];
ans -= getmin(id);
merge(id+k,2*k+1);
ans += getmin(id);
}
else
{
int id1 = ve[i][0],id2 = ve[i][1];
if(find(id1) != find(id2+k))
{
ans -= getmin(id1)+getmin(id2);
merge(id1,id2+k),merge(id1+k,id2);
ans += getmin(id1);
}
}
}
else
{
if(ve[i].size() == 1)
{
int id = ve[i][0];
ans -= getmin(id);
merge(id,2*k+1);
ans += getmin(id);
}
else if(ve[i].size() == 2)
{
int id1 = ve[i][0],id2 = ve[i][1];
if(find(id1) != find(id2))
{
ans -= getmin(id1)+getmin(id2);
merge(id1,id2),merge(id1+k,id2+k);
ans += getmin(id1);
}
}
}
printf("%d\n",ans);
}
return 0;
}