單(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 }
至此是考場上的垃圾40分代碼(高斯消元炸了)

因為解唯一且都是正整數,所以其實可以不用實數域的高斯消元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 }
我恨高斯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM