杭電多校第二場 1007 In Search of Gold
題意:
多組輸入。給定一棵樹,每條邊有兩個可能的取值a和b,整棵樹有k條邊的值來自a,其餘邊的值來自b,問樹的直徑最小值為多少?
取值範圍:k<=min(20,n-1),n<=20000,\(\sum n<=200000\)
解法:
和直徑有關,一般要用到樹上dp
發現直接求解直徑的最小值難以實現,考慮二分答案,檢驗答案是否可行。
二分樹的直徑,已知直徑,如何檢驗是否可滿足?注意到k<=20,可以採用k^2的揹包進行狀態轉移。
建立dp陣列\(dp[n][k]\),表示第n個點的子樹中,有k條邊取值來自a,點n到子樹的葉子節點的最長路。(通常情況是這樣,也有其他情況下文會說明)
對於點u,遍歷到了v,邊長度為a或b,列舉點u已經搜尋到的子樹中取a的個數和v子樹中取a的個數,可以得到狀態轉移方程:
\(dp[u][i+j+1]=min(dp[u][i+j+1],max(dp[u][i],dp[v][j]+a)\)
\(dp[u][i+j]=min(dp[u][i+j],max(dp[u][i],dp[v][j]+b)\)
因為要檢驗mid是否可行,可以在轉移之前得出此時u的子樹的直徑長度len=\(dp[u][i]+dp[v][j]+a\)或\(dp[u][i]+dp[v][j]+b\),只有在len<=mid時候才進行轉移(因為此時對應了一種在u的子樹上是可行的方案,如果不滿足上述式子,這種方案就是不可行的,不需要更新)。如果一個子樹沒有一種可行的方案,那麼mid必然就是不可滿足的。
因為在進行揹包過程時,u的子樹是不包含v的,所以不能在迴圈中直接對dp陣列進行更新,要另開一個temp陣列儲存;為了檢測該點是否有可以滿足的方案(即該點是否被更新),先將temp陣列都初始化為mid+1,這樣如果該點沒有可滿足的方案,則該點的\(dp[u][i]\)會全部被置為mid+1,在向上傳遞的過程中所有節點都將因此而不可滿足(直徑>mid),所以之後的dp值都會變成mid+1,一直傳到根節點。
程式碼:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=2e4+5; const int maxm=4e5+5; struct edge{ int next,v; ll a,b; }E[maxm]; int head[maxn],tot; void addedge(int u,int v,ll a,ll b){ E[++tot]=edge{head[u],v,a,b}; head[u]=tot; } ll dp[maxn][25]; int sz[maxn]; int n,k; void dfs(int u,int fa,ll lim){ sz[u]=0; for(int i=0;i<=k;i++){ dp[u][i]=0; } for(int ed=head[u];ed;ed=E[ed].next){ int v=E[ed].v; if(v==fa)continue; dfs(v,u,lim); int now=min(sz[u]+sz[v]+1,k); ll temp[25]; for(int i=0;i<=now;i++){ temp[i]=lim+1; } for(int i=0;i<=sz[u];i++){ for(int j=0;j<=sz[v]&&i+j<=k;j++){ if(dp[u][i]+dp[v][j]+E[ed].a<=lim){ temp[i+j+1]=min(temp[i+j+1],max(dp[u][i],dp[v][j]+E[ed].a)); } if(dp[u][i]+dp[v][j]+E[ed].b<=lim){ temp[i+j]=min(temp[i+j],max(dp[u][i],dp[v][j]+E[ed].b)); } } } for(int i=0;i<=now;i++){ dp[u][i]=temp[i]; } sz[u]=now; } } void init(int n){ memset(head,0,sizeof(int)*(n+1)); tot=0; } int main () { int T; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&k); init(n); for(int i=1;i<=n-1;i++){ int u,v; ll a,b; scanf("%d%d%lld%lld",&u,&v,&a,&b); addedge(u,v,a,b); addedge(v,u,a,b); } ll l=1,r=2e13;//左開右閉 while(r-l>1){ ll mid=(l+r)>>1; dfs(1,0,mid); if(dp[1][k]<=mid){ r=mid; } else{ l=mid; } } printf("%lld\n",r); } }