CF1290C·Prefix Enlightenment

初见安~好像也没咕很久哈。这里是传送门——Codeforces #616 Div2 E Prefix Enlightenment

Solution

这题好鬼畜……虽然挺简单但是不是那么好想。

题解参考:bilibili某神仙up主

题目有一个很重要的条件:任意三个集合的交集为空,也就是说任意一个路灯都只会属于最多两个集合。再来,假设对于路灯i有两个集合A和B都可以控制,那么就有两种情况:

1、i是关着的,那么要么A开B不开,要么A不开B开。
2、i是开着的,那么要么AB都开,要么都不开。

所以就可以看出,各个集合之间存在一种制约关系。感觉像是图论,其实并查集就可以了。这个题是扩展域带权并查集。我们将每个集合都拆成两个点【域】,一个表示操作,一个表示不操作,并且操作的话自带权值为1。我们就可以根据前面所说的制约关系连边缩点,每个集合的权值就是这样操作所需要的次数。

但是!明显是有问题的。因为如果点i只受限于一个集合,那么这个集合的两个点的状态就是固定的,也就是说有一个点代表的状态是不能选的。我们可以把这样的点连到0下面,这样的话和0相连的所有状态就都是不可以选的了。

具体实现的话会挨个枚举灯,并加入这个灯对应的集合的状态关系。当然,我们的选择可能会因为新要求的加入而改变,所以每次要先减去对于该集合的选择【也就是说该集合相关联的一系列操作的贡献都要减去,这个看代码会好理解一些。】

看代码吧。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 600005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, k, fa[maxn], size[maxn], l[maxn], r[maxn];
char s[maxn];
int get(int x) {return fa[x] == x? x : fa[x] = get(fa[x]);}

void merge(int u, int v) {//带权并查集合并
	u = get(u), v = get(v);
	if(!v) swap(u, v);
	fa[v] = u; 
	if(u) size[u] += size[v];//注意,如果u=0的话不能提供size。若cal函数加个特判倒是可以。
}

int cal(int u) {//计算集合u对应的可以提供最少操作次数的选择
	register int v;
	if(u > k) v = u - k; else v = u + k;
	u = get(u), v = get(v);//v是对立集合
	if(!u || !v) return size[u + v];//其中一个不合法,返回另一个
	return min(size[u], size[v]);//否则可以做选择
}

signed main() {//1~k是选,k+1~2k是不选
	n = read(), k = read();
	scanf("%s", s + 1);
	for(int i = 1; i <= k + k; i++) fa[i] = i;
	for(int i = 1; i <= k; i++) size[i] = 1;//选了的话权值就是1
	for(int i = 1, x, y; i <= k; i++) {
		x = read(); while(x--) {
			y = read(); if(!l[y]) l[y] = i; else r[y] = i;
		}//l是第一个集合,r是第二个集合
	}
	
	int ans = 0;
	for(int i = 1; i <= n; i++) {
		if(!r[i]) {
			register int u = l[i]; if(u) {//如果压根没有对应集合那肯定直接跳过
				ans -= cal(u);
				if(s[i] == '1') fa[get(u)] = 0;//确定对立面不能选
				else fa[get(u + k)] = 0;
				ans += cal(u);
			}
		} else {
			register int u = l[i], v = r[i];
			if(s[i] == '1') {
				if(get(u) != get(v)) {//确定没有建边过
					ans -= cal(u), ans -= cal(v);
					merge(u, v); merge(u + k, v + k);
					ans += cal(u);//两个集合合并了,一个root,直接cal是可以的
				}
			} else {
				if(get(u) != get(v + k)) {
					ans -= cal(u), ans -= cal(v);
					merge(u, v + k), merge(u + k, v);
					ans += cal(u);
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

迎评:)
——End——

发布了158 篇原创文章 · 获赞 23 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43326267/article/details/104173575
今日推荐