题意
给你
n
n
n个灯的开关状态,再给你
k
k
k个子集。选择一个子集将会按下子集中所有数字对应的开关。
m
i
m_i
mi表示前
i
i
i个灯全部开启最少要选取多少个子集,求出所有的
m
i
(
i
∈
[
1
,
n
]
)
m_i(i\in [1,n])
mi(i∈[1,n])
一些限制:
- 任意三个子集的交集都是空集。
- 一定存在一种选取方式使得所有的灯都亮着
n , k ≤ 3 e 5 n,k\le 3e5 n,k≤3e5
解题思路:
先观察限制条件:
条件1相当于一个灯最多被两个子集控制。那么如果它被两个子集控制,就在两个子集之间连一条边。如果原本灯的状态是0,那么两个子集必须2选1,如果原本状态是1,那么两个子集选取状态要相同。如果被1个子集控制,那么如果原本是0,那么这个子集必选,否则这个子集必不选。
那么就可以用并查集来维护点之间的关系,如果一个连通块内没有必选或者必不选某个点的限制,就取两种点中数目较少的点贡献答案,否则就取限制对应的点来贡献答案。条件2保证了并查集维护连通块的时候一定不会出现矛盾。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 3e5 + 50;
char s[maxn];
int fa[maxn], f[maxn], must[maxn], sz[maxn][2];
vector<int> v[maxn];
int fnd(int x){
if(x == fa[x]) return x;
int t = fa[x];
fa[x] = fnd(fa[x]);
f[x] = f[t]^f[x];
return fa[x];
}
int ans;
void link( int x, int y, int w){
int rx = fnd(x), ry = fnd(y);
if(rx == ry) return;
if(must[rx] == -1) ans -= min(sz[rx][0], sz[rx][1]);
else ans -= sz[rx][must[rx]];
if(must[ry] == -1) ans -= min(sz[ry][0], sz[ry][1]);
else ans -= sz[ry][must[ry]];
f[ry] = f[x]^f[y]^w;
fa[ry] = rx;
sz[rx][0] += sz[ry][f[ry]];
sz[rx][1] += sz[ry][f[ry]^1];
if(must[rx] == -1 && must[ry] != -1) must[rx] = must[ry]^f[ry];
if(must[rx] == -1) {
ans += min(sz[rx][0], sz[rx][1]);
}
else ans += sz[rx][must[rx]];
return;
}
int main()
{
int n, k; cin>>n>>k;
for(int i = 1; i <= k; ++i) fa[i] = i, must[i] = -1, f[i] = 0, sz[i][0] = 1;
scanf("%s", s+1);
for(int i = 1; i <= k; ++i){
int c; scanf("%d", &c);
while(c--){
int x; scanf("%d", &x);
v[x].push_back(i);
}
}
ans = 0;
for(int i = 1; i <= n; ++i){
if(s[i] == '0'){
if(v[i].size() == 1){
int x = v[i][0];
int rx = fnd(x);
if(must[rx] == -1){
must[rx] = f[x];
ans -= min(sz[rx][0], sz[rx][1]);
ans += sz[rx][must[rx]];
}
}
else {
link(v[i][0], v[i][1], 1);
}
}
else{
if(v[i].size() == 2){
link(v[i][0], v[i][1], 0);
}else if(v[i].size() == 1){
int x = v[i][0];
int rx = fnd(x);
if(must[rx] == -1){
must[rx] = f[x]^1;
ans -= min(sz[rx][0], sz[rx][1]);
ans += sz[rx][must[rx]];
}
}
}
printf("%d\n", ans);
}
}