1290C. Prefix Enlightenment(带权并查集)
一行上有n盏灯,编号从1到n。每个人的初始状态均为关闭(0)或打开(1)。
您得到了{1,2,…,n}的k个子集A1,…,Ak,因此任何三个子集的交集都是空的。换句话说,对于所有1≤i1<i2 <i3≤k,Ai1∩Ai2∩Ai3=∅。
在一项操作中,您可以选择这k个子集之一,并切换其中的所有灯的状态。对于给定的子集,可以确保使用这种类型的操作使所有的灯同时亮起。
令mi为使第i个指示灯同时点亮所需的最少操作次数。请注意,其他灯(在i + 1和n之间)的状态没有任何条件,它们可以熄灭或点亮。
您必须为所有1≤i≤n计算mi。
题解:
带权并查集的高级应用,说实话有点一知半解,很多2400分的题把基本知识点考察的很透彻。
//问题可以转化为 //每个集合拆成两个点,取点和不取点 //然后每个集合内的点,如果本来就是亮的,和不取点连边, #include<bits/stdc++.h> using namespace std; const int maxn=6e5+100; const int inf=1e9; int n,k,a[maxn]; vector<int> g[maxn]; string s; int father[maxn],w[maxn],ans; int findfather (int x) { int a=x; while (x!=father[x]) x=father[x]; while (a!=father[a]) { int z=a; a=father[a]; father[z]=x; } return x; } void Union (int x,int y) { x=findfather(x); y=findfather(y); if (x!=y) { father[x]=y; w[y]+=w[x]; } } int dsu (int x) { return min(w[findfather(x)],w[findfather(x+k)]);//这个集合取或不取的最小值 } int main () { scanf("%d%d",&n,&k); cin>>s; for (int i=1;i<=k;i++) { int x; scanf("%d",&x); for (int j=0;j<x;j++) { int y; scanf("%d",&y); g[y].push_back(i); } } for (int i=1;i<=k*2;i++) father[i]=i,w[i]=(i>k);//不取为0,取为1 w[0]=inf; for (int i=1;i<=n;i++) { if (g[i].size()==1) { //如果这个点只在一个集合 //x表示要使这个灯不亮的集合状态 int x=g[i][0]+(s[i-1]=='1')*k; ans-=dsu(g[i][0]);//答案减去这个集合取或不取的较小值 Union(x,0);//x和无限大的点连接,强制不取 ans+=dsu(g[i][0]);//答案加上这个集合取或不取的较小值 } else if (g[i].size()==2){ int x=g[i][0]; int y=g[i][1]; if (s[i-1]=='0') { if (findfather(x)!=findfather(y+k)) { ans-=dsu(x)+dsu(y); Union(x,y+k); Union(x+k,y); ans+=dsu(x); } } else { if (findfather(x)!=findfather(y)) { ans-=dsu(x)+dsu(y); Union(x,y); Union(x+k,y+k); ans+=dsu(x); } } } printf("%d\n",ans); } }