首页 > 其他分享 >CF1527D MEX Tree 题解

CF1527D MEX Tree 题解

时间:2024-01-07 19:44:48浏览次数:33  
标签:sz 200005 int 题解 sum 路径 Tree MEX size

思路

如果一条路径的 \(\text {mex} = k\),那么 \(0 \sim k-1\) 这些点一定在路径中出现过,并且一定在一条链上。如果不在一条链上,那么就不满足简单路径这一条件了。因此我们在从小到大加点的过程中如果发现一个点不在已求出的链上,那么比这个点编号大的 \(k\) 答案一定都是 \(0\) 了,因为不能同时把 \(0 \sim k-1\) 都一起走一遍,\(\text {mex}\) 也就一定 \(< k\) 了。

知道这个以后我们就得到了一个大致思路:从小到大枚举 \(\text {mex}\) 值 \(k\),同时维护一条链的左、右端点,\(k\) 的答案就是包含已求出的链并且不包含 \(k\) 的所有路径数量。求完答案再判断当前点 \(k\) 是否能与 \(0 \sim k-1\) 继续构成一条链。如果能,那就更新链的左右端点,否则直接退出,其余输出 \(0\) 即可。

那么,如何判断 \(k\) 这个点在不在已求出的链上呢?我们可以暴力跳父亲,跳的时候同时打标记,这样就保证了每个点只会经过一次,时间为 \(\mathcal {O(n)}\)。如果 \(k\) 向上跳到的第一个被打了标记的点是 \(l\) 或者 \(r\),那么从这两个端点延伸一定能到 \(k\),\(k\) 也就一定能在链上。此时就可以继续更新了。

做完这步,还有一个问题就是:如何求满足条件的路径数量?可以先从 \(0\) 开始,\(\text {mex}=0\) 的路径数量就是不经过 \(0\) 的所有路径条数。可以树形 dp 解决,设 \(f_u\) 表示以 \(u\) 为根的子树中所有路径的数量,\(g_u\) 表示以 \(u\) 为根的子树中不经过 \(u\) 的所有路径条数。那么 \(g_u=\displaystyle\sum_{v \in son_u}f_v\),$f_u=g_u+size_u-1+\dfrac{\displaystyle\sum_{v \in son_u}size_v \displaystyle\sum_{p\in son_u,p \ne v}size_p}{2} \(。\)f_u$ 的转移可以看成三部分:

  1. 所有的 \(f_v\)。
  2. 以 \(u\) 的子树中的任意一点为起点,\(u\) 为终点的路径。即 \(size_u-1\)。
  3. \(u\) 的两两儿子之间跨过 \(u\) 点相连的所有路径。那就是所有的 \(size_v \times size_p\),除以 \(2\) 是因为会算重。

这样我们就求出了 \(\text {mex}=0\) 的答案,为 \(g_0\)。因为链有两个端点,所以还要把 \(1\) 的答案求出来。对于 \(1\),与一开始说的做法一样,跳父亲跳到 \(0\),这时就已经有了链的雏形。那么,答案就是经过 \(0\) 且不经过 \(1\) 的路径条数,于是容斥一下,用所有经过 \(0\) 的路径减去既经过 \(1\) 又经过 \(0\) 的路径:\(f_0-g_0-size_1*(n-size_{now})\)。其中 \(now\) 是 \(1\) 在向上跳父亲时跳到的 \(0\) 的直连儿子。画下图就能理解了。

于是初始化左端点为 \(1\),右端点为 \(0\)。每次枚举点 \(k\),跳到链上后更新答案。怎么算答案?其实跟 \(1\) 的计算方式很像,包含以求出的链的路径且不包含 \(k\) 的路径条数。假如 \(k\) 跳到了 \(l\),那么就是 \((size_l-size_k) \times size_r\)(可能需要画图理解,有点抽象)。注意有 \(l,r\) 可能为 \(0\),此时 \(size_r,size_l\) 就要减去多的一坨,此题就结束了。

其余细节见注释,赛时写的代码,可能有点丑,见谅。

code

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int t,n,pp; 
int head[200005],cnt,k,vis[200005];
int ft[200005];
ll sz[200005];
ll f[200005],g[200005];
struct edge{
	int v,nxt;
}e[400005];
void add(int u,int v){
	e[++k].v=v;
	e[k].nxt=head[u];
	head[u]=k;
}
void dfs(int u,int fa){
	ll res=0,sum=0;
	sz[u]=1;
	ft[u]=fa;
	for(int i=head[u];~i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa)continue;
		dfs(v,u);
		g[u]+=f[v];
		sz[u]+=sz[v];
		res+=sz[v];
		sum+=sz[v]*sz[v];
	}
	res*=res;
	f[u]+=g[u]+sz[u]-1+(res-sum)/2;//计算f[u]
}
void solve(){
	cin>>n;
	k=0;
	for(int i=0;i<=n;++i){
		sz[i]=0;
		head[i]=-1;
		f[i]=g[i]=vis[i]=0;
	}
	for(int i=1;i<n;++i){
		int a,b;
		cin>>a>>b;
		add(a,b);add(b,a);
	}
	dfs(0,-1);
	cout<<g[0]<<" ";
	int now=1;
	vis[0]=1;
	while(ft[now]!=0){//跳1的父亲
		vis[now]=1;
		now=ft[now];
	}
	vis[now]=1;
	pp=now;
	cout<<f[0]-g[0]-sz[1]*(n-sz[now])<<" ";
	int l=1,r=0;//维护链的端点
	bool flag=0;
	int epos=0;
	for(int i=2;i<=n;++i){
		now=i;
		int po=0;
		while((!vis[ft[now]])){//向上跳
			po++;
			vis[now]=1;
			now=ft[now];
		}
		int ffa=ft[now]; 
		if((ffa!=l)&&(ffa!=r)&&(po||(po==0&&(!vis[i])))){//不在链上
			flag=1;
			if(r!=0)cout<<sz[l]*sz[r]<<' ';
			else cout<<sz[l]*(sz[r]-sz[pp])<<" ";
			epos=i+1;
			break;
		}
		if(now==i&&po==0&&vis[i]){//已经在链上了,那就不可能把0~k-1走完,答案为0
			cout<<0<<" ";
			continue;
		}
		vis[now]=1;
		if(ffa==l){//更新左右端点
			if(r!=0)cout<<(sz[l]-sz[i])*sz[r]<<" ";
			else cout<<(sz[l]-sz[i])*(sz[r]-sz[pp])<<' ';
			l=i;
		}
		else{
			if(r!=0)cout<<(sz[r]-sz[i])*sz[l]<<' ';
			else cout<<(sz[r]-sz[pp]-sz[i])*sz[l]<<" ";
			r=i;
		}
		
	}
	if(flag){//不在链上全部输出0
		for(int i=epos;i<=n;++i)cout<<"0 ";
		cout<<endl;
		return;
	}
	cout<<endl;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--)solve();
	return 0;
} 

标签:sz,200005,int,题解,sum,路径,Tree,MEX,size
From: https://www.cnblogs.com/sunsetlake/p/17951059

相关文章

  • [ABC329E] Stamp 题解
    正难则反。直接往上覆盖不好做,那么可以考虑把字符从\(S\)上往下删。删的过程就是在\(S\)中找\(T\)并把他们变成#。如果\(S\)中有字符为#,那我们可以把它看成任意字符,因为向上贴的过程中有重复覆盖的情况,在删的时候我们并不知道他是否重复了,所以当成任意字符来看即可(这也......
  • [ABC331F] Palindrome Query 题解
    思路判断一个字符串是否是回文串,可以从它的本质出发:正着读和倒着读是一样的。快速判断它正着和反着是否一样,用字符串哈希即可。又因为涉及单点修改,区间查询,那么使用线段树维护这两个值就行了。这里讲一下如何pushup。以正着的哈希值为例:我们要更新\(p\)这个点的\(hash\)值,......
  • DOMException - play() 请求中断
    DOMException- play()请求中断bookmark_border  FrançoisBeaufortGitHub 您刚才在Chrome开发者工具JavaScript控制台中发现了这个意外的媒体错误吗?注意 :未捕获(承诺中)DOMException:play()请求因调用pause()而中断。或注意:未捕获(在承诺中)DOMExceptio......
  • 1 月杂题题解
    好久没写博客了?今晚写爽。P5311成都七中这有黑?对于一个点\(x\),设其子树任意一点为\(y\)。我们可以求出这\(x\rightarrowy\)这条路径经过节点的的\(l,r\)。遍历\(x\)的子树,我们可以得到一些三元组\((l,r,c)\)表示\(x\)所属连通块包含了\(l,r\)这些节点,就有一......
  • [ABC273D] LRUD Instructions 题解
    [ABC273D]LRUDInstructions题解很好的一道大模拟,使我爆\(0\)。思路解析大模拟,我们只需要用一个\(x,y\)表示我们当前的位置,而对于每一个移动,我们就判断在当前移动方向上离当前点最近的点,若该点在当前行进路线上,则把当前位置设为该点前面的一个。其中判断在当前移动方向上......
  • CF1045G AI robots题解
    题目链接:洛谷或者CF本题考虑转化为cdq分治模型对于cdq分治来说,只需要考虑左边对右边的影响,那我们要考虑该怎样设置第一维度的左右对象。很显而易见的是抛开\(q\)限制而言,我们着眼于,如何让双方互相看到的严格条件转化为只需要关注单体看见。考虑什么情况下只需要一方看到......
  • CF817F MEX Queries
    题意一个集合,初始为空。请你维护以下\(3\)种操作。把\([l,r]\)中在集合中没有出现过的数添加到集合中。把\([l,r]\)中在集合中出现过的数从集合中删掉。把\([l,r]\)中在集合中没有出现过的数添加到集合中,并把\([l,r]\)中在集合中出现过的数从集合中删掉。每......
  • nested exception is java.lang.IllegalArgumentException异常问题解决
    项目启动报错如下:nestedexceptionisjava.lang.IllegalArgumentException:Couldnotresolveplaceholder'xxx'invalue"${xxx}"问题解决比较简单,只说我所遇到的情况,原因就是字母拼写问题仔细看还是能看到大写的K和小写的k有一些细微的区别,将nacos中的k和代码中修改一致后启......
  • 奇迹服务端问题解答
    1.怎么修改IIS的连接限制10个啊暂时好像没有完善的修改软件,有是有,但是我试了几次都失败,就不推荐了2.怎么修改游戏中商店出售的物品啊DMuServerDataShop1.txt~Shop11.txt3.Terrain1.Att~Terrain35.Att这些有什么用啊这个是服务端地图...4.服务端的怪物太吊了怎么修改它们LS点啊DM......
  • [ABC271E] Subsequence Path 题解
    [ABC271E]SubsequencePath题解思路解析很好的一道题,很有迷惑性,表面上是一道图论实际上是dp,定义\(f_{i}\)为从\(1\)到\(i\)的最短“好路”。先把每条边对应的起点,终点和边权记录下来,然后输入每个\(e\),由于是子序列顺序不会改变,因此可以顺序进行操作。对于每一个\(e......