一盏灯最多只会在 2 2 2个集合里出现。
设包含第 i i i盏灯的两个集合依次为 L i , R i L_i,R_i Li,Ri(若没有则值为 0 0 0)。设 o p S = 1 op_S=1 opS=1表示集合 S S S选过, o p S = 0 op_S=0 opS=0表示没选过。
对于第 i i i盏灯的情况进行讨论
- L i = 0 , R i = 0 L_i=0,R_i=0 Li=0,Ri=0,说明这盏灯不属于任何集合且 s i s_i si一定为 1 1 1,故答案不变。
- L i ≠ 0 , R i = 0 L_i\neq0,R_i=0 Li=0,Ri=0,说明这盏灯只由一个集合控制。如果 o p L i = 1 ∧ s i = 1 op_{L_i}=1\land s_i=1 opLi=1∧si=1,那么一定要撤销 L i L_i Li的选择。如果 o p L i = 0 ∧ s i = 0 op_{L_i}=0\land s_i=0 opLi=0∧si=0,那么一定要选 L i L_i Li。其余两种情况, L i L_i Li状态必须不变。前两种情况,即 o p L i = s i op_{L_i}=s_i opLi=si时会使得与 L i L_i Li有关联的集合状态发生改变,故答案会发生改变;而后两种不变。
- L i ≠ 0 , R i ≠ 0 L_i\neq0,R_i\neq0 Li=0,Ri=0,说明这盏灯只由两个集合同时控制,所以这两个集合有关联.与 2. 2. 2.同理讨论可得:如果 o p L i ⊕ o p R i = s i op_{L_i}\oplus op_{R_i}=s_i opLi⊕opRi=si,那么其中一个集合的状态一定要发生改变,优先选择改变总操作次数少且与之关联的集合能够改变状态的集合,答案加上这部分开销。否则,两个集合状态都可以不变,故答案不变。
对于关联性问题,常常使用并查集来解决。
设 C o s t u Cost_u Costu表示改变改变集合 u u u状态的总代价, C a n t u Cant_u Cantu表示集合 u u u的状态是不是不能改变。
- 根节点集合 u u u的状态要改变,则并查集里所有集合的状态都要取反,这样才能确保不影响 s 1... i s_{1...i} s1...i。花费代价 C o s t u Cost_u Costu,然后 C o s t u Cost_u Costu变为其相反数,表示之后撤销的代价.取反可以在根节点打上一个 t a g tag tag,子树里的点的实际状态都是原来的状态异或上根节点的 t a g tag tag。
- 由于有合并操作,故某点 x x x的实际状态为: x x x到根节点路径上所有节点的 t a g tag tag值的异或和。对于一个并查集,只有根节点的 t a g tag tag值在之后可能会发生改变,故可以将 x x x到根节点儿子这一段 t a g tag tag值的异或和合并起来,即路径压缩过程中不引入根节点的 t a g tag tag。
- 将并查集 x x x合并到 y y y上: C o s t \ Cost Cost累加; C a n t \ Cant Cant取或; t a g x \ tag_x tagx异或上 t a g y tag_y tagy,以确保 x x x的子节点状态不变.
时间复杂度 O ( n α ( m ) ) O(n\alpha(m)) O(nα(m))
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
typedef int arr[N];
int n, m, Ans;
char s[N];
arr L, R, fa, tag, Cant, Cost, Size;
inline int Gf(int x) {
if (!fa[x])
return x;
if (!fa[fa[x]]) // 跳过根节点的tag
return fa[x];
int p = Gf(fa[x]);
tag[x] ^= tag[fa[x]];
return fa[x] = p;
}
inline void Merge(int a, int b) {
fa[a] = b,
Size[b] += Size[a],
Cost[b] += Cost[a],
Cant[b] |= Cant[a],
tag[a] ^= tag[b];
}
inline int op(int x) { return tag[x] ^ tag[fa[x]]; }
inline int Calc(int i) {
if (!L[i])
return Ans;
if (!R[i]) {
int x = Gf(L[i]);
Cant[x] = 1;
if (op(L[i]) == (s[i] - '0'))
Ans += Cost[x], tag[x] ^= 1, Cost[x] = -Cost[x];
return Ans;
}
int x = Gf(L[i]), y = Gf(R[i]);
if (Size[x] > Size[y])
swap(x, y);
if (x == y)
return Ans;
if ((op(L[i]) ^ op(R[i])) == s[i] - '0') {
int Cx = Cost[x], Cy = Cost[y];
if ((Cx < Cy && !Cant[x]) || Cant[y])
Ans += Cx, tag[x] ^= 1, Cost[x] = -Cx;
else
Ans += Cy, tag[y] ^= 1, Cost[y] = -Cy;
}
Merge(x, y);
return Ans;
}
int main() {
scanf("%d%d%s", &n, &m, s + 1);
for (int i = 1, c; i <= m; ++i) {
scanf("%d", &c);
for (int x; c--;)
scanf("%d", &x), L[x] ? R[x] = i : L[x] = i;
}
for (int i = 1; i <= m; ++i)
Size[i] = Cost[i] = 1;
for (int i = 1; i <= n; ++i)
printf("%d\n", Calc(i));
return 0;
}