【题解】Atcoder AGC034E Complete Compress

发布时间 2023-03-30 09:37:34作者: linyihdfj

题目分析:

看到数据范围显然考虑先枚举一个集合点,也就是根。
\(g_u = \sum_{v \in tree_u \and col_u = 1} dis(u,v)\),那么我们一次操作就是让 \(g_u\) 减二或者不变,而不变的操作就是在 \(u\) 的同一棵子树内的操作是没有影响的。
因为我们可以将 \(u\) 的某一棵子树 \(v\)\(u\) 的贡献视为这个子树对应集合的大小,我们的一次操作就相当于在两个不同的集合里各自删除一个数,判断能否删完。
这个转化后的问题,显然若最大的集合的大小不超过其他集合的大小之和就可以删完,或者如果 \(g_u\) 为奇数就删的只剩一个。
而我们发现其实每一棵子树对应的集合的大小是不一定的,因为我们可以在子树内部删除,也就是设 \(f_u\) 表示在 \(u\) 的子树内经过操作之后 \(g_u\) 也就是集合大小的最小值,那么对于 \(u\) 来说它对应的子树大小就是 \([f_u,g_u]\),而我们为了尽可能使得上面的条件满足,很显然每次都用当前子树的最小大小和其他子树的最大大小比较是最有可能的。
形式化的来说若 \(\forall v \in son_u f_v + sz_v \le g_u - g_v - sz_v\) 那么即每一棵子树对应的集合大小不超过其他子树对应的集合大小的和,也就是可以删完,否则若存在某一个子树不满足条件,那么就不能删完,也就是考虑怎么已知子树求解 \(f_u\),因为若根的 \(f\)\(0\) 即代表可以在根集结,而答案就是根对应的 \(\frac{g}{2}\)
若可以删完,则 \(f_u\) 等于 \(g_u\) 的奇偶性,这个是显然地。
若不可以删完,且不满足的子树为 \(v\),则 \(f_u = f_v + sz_v - (g_u - g_v - sz_v)\),其实不能删完就是看全部去删 \(v\) 的情况下,\(v\) 还剩多少。因为不满足的条件可以转化为大于整体的一半,所以最多只有一个 \(v\) 满足条件。
最后判断一下我们根的 \(f\) 是否为 \(0\) 然后统计一下答案就好了,时间复杂度 \(O(n^2)\)

但是其实这个题可以做到 \(O(n)\),因为我们发现无论是 \(f\) 还是 \(g\) 都可以实现换根后快速求解,所以随便选一个根 \(dfs\) 之后换根 \(dp\) 就好了。
具体的换根做法其实就是观察我们的判断能不能删完的条件可以转化为:\(f_v + 2\times sz_v + g_v > g_u\),而显然只有最大的 \(f_v + 2\times sz_v + g_v\) 才有可能,而我们换根有可能当前的根就对应最大的 \(v\),所以为了便于求解应该再维护一个次大的 \(v\)
维护完了这个之后,在根 \(u \to v\) 的时候就可以快速得到 \(u\) 这棵子树的信息,从而将 \(u\) 合并到 \(v\) 上得到 \(v\) 作为根的信息。

代码:

不换根:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+5;
const int INF = 1e9+5;
struct edge{
	int nxt,to;
	edge(){}
	edge(int _nxt,int _to){
		nxt = _nxt,to = _to;
	}
}e[2 * N];
int cnt,head[N],f[N],sz[N],sum[N];
char s[N];
void add_edge(int from,int to){
	e[++cnt] = edge(head[from],to);
	head[from] = cnt;
}
void dfs(int now,int fath){
	f[now] = 0;sz[now] = s[now] == '1';sum[now] = 0;
	for(int i = head[now]; i; i = e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		dfs(to,now);
		sum[now] += sum[to] + sz[to]; //求总的贡献 
		sz[now] += sz[to];
	}
	for(int i = head[now]; i;i = e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		//这个子树的最小大小     其他子树的大小 
		if(f[to] + sz[to] > sum[now] - sum[to] - sz[to]){
			f[now] = f[to] + sz[to] - (sum[now] - sum[to] - sz[to]);
		}
	}
	if(!f[now])	f[now] = sum[now] & 1;
}
int main(){
	int n;scanf("%d",&n);
	scanf("%s",s+1);
	for(int i=1; i<n; i++){
		int from,to;scanf("%d%d",&from,&to);
		add_edge(from,to);add_edge(to,from);
	}
	int ans = INF;
	for(int i=1; i<=n; i++){
		dfs(i,0);
		if(f[i] == 0)	ans = min(ans,sum[i] / 2);
	}
	printf("%d\n",ans == INF ? -1 : ans);
	return 0;
}

换根:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4+5;
const int INF = 1e9+5;
struct edge{
	int nxt,to;
	edge(){}
	edge(int _nxt,int _to){
		nxt = _nxt,to = _to;
	}
}e[2 * N];
int cnt,head[N],f[N],sz[N],sum[N],mx[N][3],nmx[N][3],ans[N];
char s[N];
void add_edge(int from,int to){
	e[++cnt] = edge(head[from],to);
	head[from] = cnt;
}
void dfs(int now,int fath){
	f[now] = 0;sz[now] = s[now] == '1';sum[now] = 0;
	for(int i = head[now]; i; i = e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		dfs(to,now);
		sum[now] += sum[to] + sz[to]; //求总的贡献 
		sz[now] += sz[to];
	}
	for(int i = head[now]; i;i = e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		//这个子树的最小大小     其他子树的大小 
		if(f[to] + sz[to] > sum[now] - sum[to] - sz[to]){
			f[now] = f[to] + sz[to] - (sum[now] - sum[to] - sz[to]);
		}
		int tmp = f[to] + 2 * sz[to] + sum[to];
		if(tmp>mx[now][2]){
			nmx[now][1] = mx[now][1];nmx[now][2] = mx[now][2];
			mx[now][1] = to;mx[now][2] = tmp;
		}
		else if(tmp>nmx[now][2]){
			nmx[now][1] = to;nmx[now][2] = tmp;
		}
	}
	if(!f[now])	f[now] = sum[now] & 1;
}
void dp(int now,int fath){
	if(now != 1){
		int tmpsz = sz[fath] - sz[now],tmpsum = sum[fath] - sum[now] - sz[now],tmpf;   //关键是得到 fath 这棵新子树的信息 
		if(now == mx[fath][1]){
			if(nmx[fath][2] > tmpsum) tmpf = nmx[fath][2] - tmpsum;
			else	tmpf = tmpsum & 1;
		}
		else{
			if(mx[fath][2] > tmpsum) tmpf = mx[fath][2] - tmpsum;
			else	tmpf = tmpsum & 1;
		}
		
		sum[now] = sum[fath] - sz[now] + (sz[fath] - sz[now]),sz[now] = sz[fath];  //将有可能的这三棵子树放到一起重新做一遍 
		f[now] = 0;
		int tmp = tmpf + 2 * tmpsz + tmpsum;
		if(tmp > mx[now][2]){
			nmx[now][1] = mx[now][1],nmx[now][2] = mx[now][2];
			mx[now][1] = fath,mx[now][2] = tmp;
		}
		else if(tmp > nmx[now][2]){
			nmx[now][1] = fath,nmx[now][2] = tmp;
		}
		if(mx[now][2] > sum[now]){
			f[now] = mx[now][2] - sum[now];
		}
		if(!f[now])	f[now] = sum[now] & 1;
		if(f[now] == 0)	ans[now] = sum[now] / 2;
		else	ans[now] = INF;
	}
	for(int i=head[now]; i;i = e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		dp(to,now);
	}
} 
int main(){
	int n;scanf("%d",&n);
	scanf("%s",s+1);
	for(int i=1; i<n; i++){
		int from,to;scanf("%d%d",&from,&to);
		add_edge(from,to);add_edge(to,from);
	}
	dfs(1,0);
	if(f[1] == 0)	ans[1] = sum[1] / 2;
	else	ans[1] = INF;
	dp(1,0);
	int res = INF;
	for(int i=1; i<=n; i++)	res = min(res,ans[i]);
	printf("%d\n",res == INF ? -1 : res);
	return 0;
}