单(single)

题很好,让我发现自己没有一些套路意识。

先沾一下正版题解

标签:dfs,树形DP,高斯消元
分析:本题是一道二合一的问题,两个子任务相互对称.
算法1:
t=0的数据最直接的想法是从每个点出发做一遍dfs,时间复杂度O(n^2),可以通过第1个测试点,期望得分10分
算法2:
t=1的数据最直接的想法是枚举所有可能的a[]数组判断是否可行.第2个测试点n<=5,1<=a[i]<=20.注意20^5=3200000,直接暴力搜索a[i]的取值是可以承受的,可以通过第2个测试点,期望得分10分,结合算法1,期望得分20分.
算法3:
t=1的数据中,b数组的表达式写出来之后,每个b[i]对应一个关于a[i]和树上两点距离的方程,例如b[1]=dis(1,1)*a[1]+dis(1,2)*a[2]+dis(1,3)*a[3]+...+dis(1,n)*a[n],于是可以列出n个方程用高斯消元求解,可以通过第2,3,4个测试点,期望得分30分,结合算法1,期望得分40分.
算法4:
对于测试点5,树退化为1条链。这个测试点的作用主要在于启发选手想到正解的思路。
t=0时,我们分别考虑编号小于i和大于i的点对b[i]的贡献.那么离得越远的点对答案的贡献越大.分别考虑每条边对答案的贡献,那么左侧的某条边对答案的贡献就是这条边左侧全部a[i]之和.实际上,我们对a[i]分别求取两次前缀和,两次后缀和即可完成对b[i]的计算.
记suf(i)为a[i]+a[i+1]+....+a[n-1]+a[n],pre(i)为a[1]+a[2]+...+a[i-1]+a[i],则b[i]=pre(1)+pre(2)+...+pre(i-1)+suf(i+1)+suf(i+2)+...+suf(n-1)+suf(n)
考虑t=1的情况.
我们知道suf(2)+suf(3)+...+suf(n)的值(即b[1]),知道pre(1) + suf(3) + suf(4) +...+suf(n)的值(即b[2]),知道pre(1)+pre(2)+suf(4)+...+suf(n)的值(即b[3]),注意到这些式子有很多项是一样的,考虑作差.可以得到下面的式子:
b[2]-b[1]=pre(1)-suf(2),b[3]-b[2]=pre(2)-suf(3).....b[i+1]-b[i]=pre(i)-suf(i+1)                   
这些式子是有实际意义的,考虑从b[1]变到b[2]时答案的变化量,变化的就是1和2之间连边的贡献.
同时,记SUM=a[1]+a[2]+...+a[n-1]+a[n],显然pre(i)+suf(i+1)=SUM,因此pre(i)=SUM-suf(i+1),将上面式子中所有pre换成suf,我们就知道了
SUM-2*suf(2) ,SUM-2*suf(3)...SUM-2*suf(n)的取值。
注意我们将n个式子作差之后得到了n-1个等式,实际上是丢弃了一些信息,只保留了b[i]之间的相对大小而忽略了b[i]自身的数值大小.于是考虑b[1]=suf(2)+suf(3)+suf(4)+... +suf(n),我们发现suf(2)到suf(n)都恰好出现了一次,而之前得到了n-1个形如SUM-2*suf(i)的式子,将这n-1个式子相加,suf(2),suf(3)...suf(n)前面的系数都是2,SUM的系数为(n-1),那么把这个式子加上2*b[1],就得到了(n-1)*SUM,将求得的SUM代入之前的n-1个式子可以得到suf(2),suf(3),suf(4)......suf(n),差分一下即可得到a数组.
时间复杂度O(n),可以通过第5个测试点.推出这个做法,树上的做法也就不难想了.
算法5(满分算法):
考虑树上的问题.
t=0的时候,我们可以先暴力计算出b[1],然后从b[1]出发开始dfs,由某个点的父亲的b[i]推出这个点的b[i],变化的是这个点和父亲的连边的贡献,也就是这条边两侧的点的a[i]之和的差值.
t=1的时候,顺着链上的思路,我们先随便找一个点为根建树,将有边直接相连的两个点的b[i]作差.设x的父亲为prt[x],以x为根的子树中所有a[i]之和为sum(x), SUM=a[1]+a[2]+...+a[n-1]+a[n],那么b[x]-b[prt[x]]=(SUM-sum(x))-sum(x).
同链的情况一样,得到n-1个式子,将树根的b[i]也列出式子,可以求出全部a[i]之和,然后就可以根据之前的式子推出所有的a[i],和链的做法类似,不再赘述.
本人的懒癌行为

 

首先暴力分是比较好拿的,看到{<=30000 所有边满足v=u+1}以为是个区间可O(1)查询dis数组(哭笑不得),然鹅其实并不能苟到分。回想之前做题经验,还是有这种用特殊数据范围提示正解思路的题的,确实是好题。首先对于特殊数据的分析,上面解释的还是很清楚,然后我就膨胀了,关掉题解自己安心码了扩展成树的情况,从左右端点关系转移到父子关系的式子是对的

b[i]-b[i-1]=SUM-2*sma[i]

 到

b[tto]-b[u]=SUM-2*sma[tto]

 然而求SUM的化简式子已经变了,不再满足链的特殊性,链可以做到b[1]+b[n]=SUM,然而树的话直接无脑将b[tto]-b[u]全加进去就可以了(做的时候10个人A,做完时已经基本全A了,蓝瘦

#include<bits/stdc++.h>
#define MAXN 100010
#define int long long
using namespace std;
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar(); }
	while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
struct rr{
	int nt,to;
}bl[MAXN<<1];int hd[MAXN],itot;
void add(int x,int y){
	bl[++itot].to=y;
	bl[itot].nt=hd[x];
	hd[x]=itot;
}
int T,n;
int a[MAXN],b[MAXN];
int sma[MAXN];
int SUM;
void yu_sou1(int u,int fa){
	int tto;
	sma[u]=a[u];
	for(int i=hd[u];i;i=bl[i].nt){
		tto=bl[i].to;
		if(tto==fa)continue;
		yu_sou1(tto,u);
		sma[u]+=sma[tto];
		b[1]+=sma[tto];//root 的暴力答案
	}
	//printf("sma[%d]=%d
",u,sma[u]);
	return ;
}
void sou1(int u,int fa){
	int tto;
	for(int i=hd[u];i;i=bl[i].nt){
		tto=bl[i].to;
		if(tto==fa)continue;
		b[tto]=b[u]-2*sma[tto]+SUM;
		sou1(tto,u);
	}
}
void yu_sou2(int u,int fa){//1 jia 0 jian
	int cnt=0;
	int tto;
	for(int i=hd[u];i;i=bl[i].nt){
		tto=bl[i].to;
		if(tto==fa)continue;
		SUM+=b[tto]-b[u];
		yu_sou2(tto,u);
	}
}
void sou2(int u,int fa){
	int tto;
	a[u]=sma[u];
	//printf("sma[%d]=%d
",u,sma[u]);
	for(int i=hd[u];i;i=bl[i].nt){
		tto=bl[i].to;
		if(tto==fa)continue;
		sma[tto]=(SUM-b[tto]+b[u])/2;
		a[u]-=sma[tto];
		sou2(tto,u);
	}
	//printf("a[%d]=%d
",u,a[u]);
}
void shan(){
	itot=0;
	memset(hd,0,sizeof(hd));
	memset(sma,0,sizeof(sma));
}
signed main(){
//	freopen("da.in","r",stdin);
	T=read();
	while(T){
		--T;
		shan();
		n=read();
		for(int i=1,u,v;i<n;++i){
			u=read();v=read();
			add(u,v);add(v,u);
		}
		int opt=read();
		SUM=0;
		switch(opt){
			case 0:{
				for(int i=1;i<=n;++i)
					a[i]=read(),b[i]=0,SUM+=a[i];
				yu_sou1(1,0);
				sou1(1,0);
				for(int i=1;i<=n;++i)
					printf("%lld ",b[i]);
				puts("");
				break;
			}
			case 1:{
				for(int i=1;i<=n;++i)
					b[i]=read(),a[i]=0;
				yu_sou2(1,0);
				SUM+=2*b[1];
				SUM/=n-1;
				//cout<<SUM<<endl;
				sma[1]=SUM;
				sou2(1,0);
				for(int i=1;i<=n;++i)
					printf("%lld ",a[i]);
				puts("");
				break;
			}
		}
	}
}

 补充,这种树的题都把难维护的和边有关的东西考虑成一跳边对整体的贡献

原文地址:https://www.cnblogs.com/2018hzoicyf/p/11256815.html