单(single):换根dp,表达式分析,高斯消元
虽说这题看大家都改得好快啊,但是为什么我感觉这题挺难。(我好菜啊)
所以不管怎么说那群切掉这题的大佬是不会看这篇博客的所以我要开始自嗨了。
这题,明显是树dp啊。只不过出题人想看你发疯,询问二合一了而已。
对于给出了a数组要求b数组的询问,想象一下怎么求。
你先yy一棵树,我懒得画了。。。父节点叫fa,子节点叫s
那么想一想对于s来说它的答案来自与哪里。
首先是它的子树,设以s为根的子树的a值和为w[s],而子树对它的总贡献是son[s]
那么这样理解:所有子树里的点都需要先走到s的直接儿子们,然后再从直接儿子上走一步到s
这样的话,son[s]就成为了b[s]的答案的一部分
1 void dfs(cri p,cri fa){ 2 w[p]=a[p]; 3 for(int i=fir[p];i;i=l[i])if(to[i]!=fa) 4 dfs(to[i],p),w[p]+=w[to[i]],son[p]+=son[to[i]]+w[to[i]]; 5 b[p]=son[p]; 6 }
然后考虑完子树的贡献以后,就是子树以外的部分了。
根节点的b数组已经处理出来了,因为根的全部答案都来自于它的子树。
然后我们从上往下dfs,假定我们已经知道了b[fa],我们需要知道b[s]
当时在上一个dfs时这个儿子给父亲的b的贡献是son[s]+w[s]。除去这些贡献,剩下的都是这棵子树以外的贡献了。
这个值代表什么呢?就是从s子树以外的点走到fa需要的总费用啦。
我们现在需要的是从s子树以外的所有点走到s的费用,加上son[s]就是b[s]了。
那么就比较明显了,从fa再走一步到s即可,付出的代价就是子树以外所有点的a值和,即w[1]-w[s](假定从1为整棵树开始dfs)
1 void Dfs(cri p,cri fa){ 2 for(int i=fir[p];i;i=l[i])if(to[i]!=fa) 3 b[to[i]]+=b[p]-son[to[i]]-w[to[i]]+w[1]-w[to[i]],Dfs(to[i],p); 4 }
这样的话我们就十分顺利的求解出了b数组。
接下来需要用b数组求解a数组。
正解不太好想,还是先讲最普通的高斯消元。
暗中观察数据范围,所有t=1的点它的n都不大,可以高斯消元。
我们n2求出所有点对的距离(n遍dfs),构造距离方程组,带入b数组,高斯消元求解。
1 #include<cstdio> 2 #define cri const register int 3 int T,t,n,a[100005],b[100005],cnt;double dt[105][105]; 4 int son[100005],fir[100005],l[200005],to[200005],w[100005]; 5 void connect(cri a,cri b){ 6 l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b; 7 l[++cnt]=fir[b];fir[b]=cnt;to[cnt]=a; 8 } 9 void dfs(cri p,cri fa){ 10 w[p]=a[p]; 11 for(int i=fir[p];i;i=l[i])if(to[i]!=fa) 12 dfs(to[i],p),w[p]+=w[to[i]],son[p]+=son[to[i]]+w[to[i]]; 13 b[p]=son[p]; 14 } 15 void Dfs(cri p,cri fa){ 16 for(int i=fir[p];i;i=l[i])if(to[i]!=fa) 17 b[to[i]]+=b[p]-son[to[i]]-w[to[i]]+w[1]-w[to[i]],Dfs(to[i],p); 18 } 19 void DFS(cri p,cri fa,cri be,cri dtt){ 20 dt[be%n+1][p]=dtt; 21 for(int i=fir[p];i;i=l[i])if(to[i]!=fa)DFS(to[i],p,be,dtt+1); 22 } 23 void Gauss(){ 24 for(int i=1;i<=n;++i){ 25 int best=i;double res; 26 for(int j=i+1;j<=n;++j)if(dt[j][i]>dt[best][i])best=j; 27 for(int j=i;j<=n+1;++j)res=dt[best][j],dt[best][j]=dt[i][j],dt[i][j]=res; 28 for(int j=n+1;j>=i;--j)dt[i][j]/=dt[i][i]; 29 for(int j=1;j<=n;++j)if(i!=j)for(int k=n+1;k>=i;--k)dt[j][k]-=dt[i][k]*dt[j][i]; 30 } 31 } 32 int main(){ 33 scanf("%d",&T); 34 while(T--){ 35 scanf("%d",&n); 36 for(int i=1,aa,bb;i<n;++i)scanf("%d%d",&aa,&bb),connect(aa,bb); 37 scanf("%d",&t); 38 if(!t){ 39 for(int i=1;i<=n;++i)scanf("%d",&a[i]); 40 dfs(1,0);Dfs(1,0); 41 for(int i=1;i<=n;++i)printf("%d ",b[i]);puts(""); 42 } 43 else{ 44 for(int i=1,aa;i<=n;++i)scanf("%d",&aa),dt[i%n+1][n+1]=aa,DFS(i,0,i,0); 45 Gauss(); 46 for(int i=1;i<=n;++i)printf("%.0lf ",dt[i][n+1]);puts(""); 47 } 48 for(int p=1;p<=n;++p)son[p]=fir[p]=w[p]=a[p]=b[p]=0;cnt=0; 49 for(int i=1;i<=100;++i)for(int j=1;j<=101;++j)dt[i][j]=0; 50 } 51 }
因为解唯一且都是正整数,所以其实可以不用实数域的高斯消元double卡精度,我们可以来一个整数域的。
原来你的高斯消元大概是这个样子的:
5x+y=8;2x+3y=11;
首先你选中了x绝对值最大的那个式子,把系数变为1。(有的板子没有这一步)
x+0.2y=1.6;2x+3y=11;
然后你又会拿第一个式子去消第二个的x。得到:
x+0.2y=1.6;2.6y=7.8;
y就出来了,是3,再回代得x是1。
多好的整数啊,但是你的高斯消元里面有很多浮点数运算,精度容易下降。
既然是整数,我们就用整数的方法做啊。想想你人工求解的过程:
还是这个式子:5x+y=8;2x+3y=11;
你会把它做类似于通分的操作,10x+2y=16;10x+15y=55;
虽然数字大了一点,但是是整数啊,只要没爆long long都没有问题。
做差,顺便还原前面那个式子:5x+y=8;13y=39;
呃啊,舒服。具体的代码实现也挺简单的,求个lcm就好了,用a*b/gcd(a,b)就行
暴力算法说多了。
测试点5,它连成了一条可爱的链。
我说过,
学长说过(为了更有说服力),数据范围不是白给的,有些具有提示作用。
那就肝它啊!肝出正解为止!
对于一条链我们现在知道它的b数组。求解a?
像刚才已知a数组求解b数组一样,我们设1是根而n是叶子。不断计算贡献是怎么叠加,去除的。
那么b数组的来源格外清晰明了:
设pre[i]为前i个节点的a值和,suc[i]为i~n的a值和。
b[i]=pre[1]+pre[2]+...+pre[i-2]+pre[i-1]+suc[i+1]+suc[i+2]+..+suc[n](原始式子)
再写一个
b[i-1]=pre[i]+pre[2]+...+pre[i-2]+suc[i]+suc[i+1]+suc[i+2]+...+suc[n]
数学上的什么错位相减。
b[i]-b[i-1]=pre[i-1]-suc[i](差值式子)
这个式子里面未知量不多,类似的我们可以列出一共n-1个这样的式子
可是里面的pre和suc都不一样不是很好求解。
设sum为所有点的a值和。根据定义的含义pre[i]+suc[i+1]=sum;->pre[i-1]=sum-suc[i]
那么上面的那个式子可以略微化简b[i]-b[i-1]=sum-2*suc[i];
类似的,我们还是有n-1个式子,它们现在有一个共同的未知量sum,这就好做一些了
只要知道sum,就能知道suc,就能知道a
但是,我们现在一共有n个未知量却只有n-1个式子,解不出来。
强行加一个!
我们一直运用的都是两个原始式子做差求得的差值式子,其实在这个过程中我们就不小心抛弃了某些有用的条件。
我们捡回原始式子,看哪个还能用?
b[1]=suc[2]+suc[3]+...+suc[n]
这个看起来比较漂亮。我们把我们的n-1个差值式子放在一起
b[n]-b[n-1]=sum-2*suc[n]
b[n-1]-b[n-2]=sum-2*suc[n-1]
b[n-2]-b[n-3]=sum-2*suc[n-2]
...
b[2]-b[1]=sum-2*suc[2]
左边这些东西一正一负的,把它们加起来貌似会消的挺干净
b[n]-b[1]=(n-1)*sum-2*(suc[n]+suc[n-1]+...+suc[3]+suc[2])
把那个能用的原始式子拿过来看一看:b[1]=suc[2]+suc[3]+...+suc[n]
后面的那一串suc是完全重复的!带进去!
b[n]-b[1]=(n-1)*sum-2*b[1]
sum=$ \frac{b[1]+b[n]}{n-1} $
漂亮啊!sum出来了,接下来每个差值式子就都只有一个未知量了
然后根据suc数组做差a数组就出来了,问题解决了!
于是我们折腾了这么半天,终于愉快的得到了...额外的10分
都说了这种测试数据是用来启发你的。
其实在树上这个式子也没有什么差别,只不过suc数组的含义变成了子树,即上述只是以1为根的特殊情况。
其实就和用a求b里面的那个son数组是一样的了
反正没人看,撇一个代码就溜啦。
1 #include<cstdio> 2 #define int long long 3 #define cri const register int 4 int T,t,n,a[100005],b[100005],cnt,SUM; 5 int son[100005],fir[100005],l[200005],to[200005],w[100005]; 6 void connect(cri a,cri b){ 7 l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b; 8 l[++cnt]=fir[b];fir[b]=cnt;to[cnt]=a; 9 } 10 void dfs(cri p,cri fa){ 11 w[p]=a[p]; 12 for(int i=fir[p];i;i=l[i])if(to[i]!=fa) 13 dfs(to[i],p),w[p]+=w[to[i]],son[p]+=son[to[i]]+w[to[i]]; 14 b[p]=son[p]; 15 } 16 void Dfs(cri p,cri fa){ 17 for(int i=fir[p];i;i=l[i])if(to[i]!=fa) 18 b[to[i]]+=b[p]-son[to[i]]-w[to[i]]+w[1]-w[to[i]],Dfs(to[i],p); 19 } 20 void DFS(cri p,cri fa){ 21 if(p!=1)SUM+=b[p]-b[fa]; 22 for(int i=fir[p];i;i=l[i])if(to[i]!=fa)DFS(to[i],p); 23 } 24 void dFs(cri p,cri fa){ 25 if(p!=1)a[p]=son[p]=(SUM-b[p]+b[fa])>>1;else a[p]=son[p]=SUM; 26 for(int i=fir[p];i;i=l[i])if(to[i]!=fa)dFs(to[i],p),a[p]-=son[to[i]]; 27 } 28 main(){ 29 scanf("%lld",&T); 30 while(T--){ 31 scanf("%lld",&n); 32 for(int i=1,aa,bb;i<n;++i)scanf("%lld%lld",&aa,&bb),connect(aa,bb); 33 scanf("%lld",&t); 34 if(!t){ 35 for(int i=1;i<=n;++i)scanf("%lld",&a[i]); 36 dfs(1,0);Dfs(1,0); 37 for(int i=1;i<=n;++i)printf("%lld ",b[i]);puts(""); 38 } 39 else{ 40 for(int i=1;i<=n;++i)scanf("%lld",&b[i]); 41 DFS(1,0);SUM+=b[1]*2;SUM/=(n-1);dFs(1,0); 42 for(int i=1;i<=n;++i)printf("%lld ",a[i]);puts(""); 43 } 44 for(int p=1;p<=n;++p)son[p]=fir[p]=w[p]=a[p]=b[p]=0;cnt=SUM=0; 45 } 46 }