长乐爆零之旅 day5 苹果树

版权声明:八月炊火的博客如需转载请注明出处 https://blog.csdn.net/qq_34990731/article/details/82947267

这一题是树上差分和lca,可是没看出来,一开始直接暴力了。
题目描述:
小 B 是一个辛勤的农民,他家里种了一棵很大的苹果树。 这棵苹果树可以看作一张 n 个点 n-1 条边的无向连通图,小 B 觉得这颗苹果树很脆弱,因为只要剪断任意一条边,苹果树就不连通了,于是他给苹果树新加了 m 条边。现在这颗苹果树就不像是一棵树了,成了一张 n 个点 n+m-1 条边的无向连通图,小 Q是小 B 的好朋友,他觉得这棵树依然很脆弱,他告诉小 B,自己只要破坏两条边,一条是原树中的边,一条是小 B 新加的边,就能使苹果树不连通。 小 B 觉得小 Q 说得很不可思议,于是他找来了擅长编程的你,希望你告诉他,按照小B 的破坏规则,有多少种不同的方案使得苹果树不连通。 注意:两种方案不同当且仅当一条边在第一种方案中被删除了但在第二种方案中没有被 删除。
输入格式:
第一行两个正整数 n,m,表示苹果树的点数和后面新加的边的个数。 接下来 n-1 行,每行两个正整数 u,v,表示(u,v)之间有一条树边。 接下来 m 行,每行两个正整数 u,v,表示(u,v)之间有一条小 B 新加的边。
输出格式:
输出一行一个整数,表示有多少种不同的方案。
输入样例:
4 1
1 2
2 3
1 4
3 4
输出样例:
3
在这里插入图片描述
题解:这一题打死我也看不出来是树上差分,那些dalao真是太厉害了。
我们先讲讲为什么要用树上差分(不会树上差分的话我博客还没写,如果写了链接会放在这个位置),我们先不看新加的边,如果只是原来的那几条边的话,无论断哪一条都会成立,接下来看看新加的边,我们可以发现(然而我当时并没有发现 )如果加了一条边那么它起到的作用将与一条链,举个栗子:如果我们在x,y这边加一条边,那么这条边相当于x到y原树上的链的作用(两点在树上只有唯一 一条链就不用我说了吧),这样子我们在加完所有的边后,我们发现原本树上的边起到的作用可能会被不止一条新加的边替代,那么我们可以发现,如果这一条边的作用没有任何一条新边代替(意味着这一条边断了,整个图就不连通了),那么无论另一条我们选哪一条都会不连通,那么方案数就有m种;如果一条边的作用刚好被另一条新边替代,那么很明显只要把这两条都删了就不连通了,那么方案数是1;如果有一条边作用被两条新边(或者以上)替代的话,无论怎么删除都会联通,所以这个方案数为0。
接下来我们只要统计一下所有的原边的作用被多少新边所替代就ojbk了,下面讲怎么统计。
我们定义一个much数组,much[i]表示第i个点到自己父亲的那一条边的作用被多少新边替代(因为父亲只有一个,所以这样的边也只有唯一 一条),然后我们在读入新边的时候树上差分统计就好了。下面上代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<queue>
using namespace std;
int head[600100],Next[600100],ver[600100],much[300100],size=0,t,f[300100][20],deep[300100],n,m,vist[300100];//much数组存对应边的作用被多少条边替代
//f数组存倍增的祖先,即f[i][j]表示i这个节点第2^j个祖先;deep存深度;vist存访问 
queue <int> q;//倍增预处理用的 
void add(int x,int y)//边表常规操作 
{
	size++;
	Next[size]=head[x];
	head[x]=size;
	ver[size]=y;
	return ;
}
void bfs()//我们预处理倍增 
{
	deep[1]=1;
	q.push(1);
	while(q.size())
	{
		int x=q.front();
		q.pop();
		for(int i=head[x];i;i=Next[i])
		{
			int y=ver[i];
			if(deep[y]!=0)
				continue;
			deep[y]=deep[x]+1;//深度确定 
			f[y][0]=x;//dp的初始值 
			for(int j=1;j<=t;j++)//开始dp操作 
				f[y][j]=f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
	return ;
}
int  lca(int x,int y)//询问两个点的lca 
{
	if(deep[x]>deep[y])//我们确保y的深度比x大 
		swap(x,y);
	for(int i=t;i>=0;i--)//把y不断往上提 
		if(deep[f[y][i]]>=deep[x])
			y=f[y][i];
	if(x==y)//如果x和y相等,即x是y的祖先,直接返回x 
		return x;
	for(int i=t;i>=0;i--)//我们现在确定x,y同一深度后同时往上提 
		if(f[y][i]!=f[x][i])//只要两个的祖先不同就可以提 
		{
			y=f[y][i];
			x=f[x][i];
		}
	return f[x][0];//我们可以确保x,y一定为lca的儿子,直接返回父亲 
}
void dfs(int x)//我们把差分统计一下 
{
	vist[x]=1;
	for(int  i=head[x];i;i=Next[i])
	{
		int y=ver[i];
		if(vist[y]==1)
			continue;
		dfs(y);
		much[x]+=much[y];//在回溯时(确保儿子已经计算完了)自加上儿子(差分的常规操作) 
	}
}
int main()
{
	long long ans=0;
	scanf("%d %d",&n,&m);
	t=(int)(log(n)/log(2)+1);//先计算一下log 2 n,后面倍增dp和上提要用 
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);//建立树 
		add(x,y);
		add(y,x);
	}
	bfs();
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);//加入新边 
		much[lca(x,y)]-=2;//在lca上-2(-2的原因是因为两个后代各自+1,所以祖先要-2) 
		much[x]++;//后代++ 
		much[y]++;
	}
	dfs(1);//dfs统计差分结果 
	for (int i=2;i<=n;++i)//循环判断,much[1]指根到自己父亲的,这一条边其实是不存在的不过我们为了代码好打,同一,所以虚拟了这个边,记得不要加 
		if (much[i]==1) //如果有一条边代替 
			ans++;
		else if (!much[i]) //如果有零条边代替 
			ans+=m;
	printf("%lld",ans);//给波答案 
	return 0;
} 

唉,还有两天国庆就gg了,伤心,蓝过。

猜你喜欢

转载自blog.csdn.net/qq_34990731/article/details/82947267