1. 程式人生 > 實用技巧 >杭電多校第二場 1007 In Search of Gold

杭電多校第二場 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);
    }
}