1. 程式人生 > >WannaFly挑戰賽 28 C msc的寵物(二分答案 + 樹形dp)

WannaFly挑戰賽 28 C msc的寵物(二分答案 + 樹形dp)

題意相當於是把一棵樹分為k+1個部分,然後使得這麼多個部分的極差最大值最小。

首先考慮直接樹形dp,由於是在討論極差,所以我們要想辦法表示極差的狀態,但是我們發現僅僅用狀態表示極差,並不能夠很好的轉移,而且極差本身也不好表示。在轉移的時候,滿足一個子樹的極差最小值,在考慮別的節點的時候可能並不是最小值,也就是說子問題是有後效性的,這也就意味這直接用dp是做不了的。

所以我們考慮二分答案,轉換一下求的東西。我們二分最大的極差,然後考慮在當前極差的情況下最少能夠分成多少個部分,或者說最少需要剪斷幾條邊。我們令dp[x][i]表示處理完x的子樹,且x所在部分的最小值為i,最少需要減去的邊數。顯然,如果點i的權值大於點x,那麼這個狀態是非法的標記為INF。然後考慮轉移,顯然有兩種方式,對於x的任意兒子y,要麼和x在同一個部分,要麼不在同一個部分。所以有轉移方程dp[x][i]+=min(dp[y][i],f[y]+1),其中f[y]表示min(dp[y][k])。即如果和x在同一個部分,那麼切斷的邊數就要考慮上y的子樹對應情況下切斷的邊數;如果不在一個部分,那麼考慮上y的子樹的邊數的同時還要加上1表示切斷x與y相連的邊。

如此轉移,最後的f[1]就表示處理完整棵樹之後所需要切斷的邊的最小條數,與給定的k進行比較即可。時間複雜度O(N^2).具體見程式碼:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define pi 3.141592653589793
#define mod 998244353
#define LL long long
#define pb push_back
#define lb lower_bound
#define ub upper_bound
#define sf(x) scanf("%d",&x)
#define sc(x,y,z) scanf("%d%d%d",&x,&y,&z)

using namespace std;

const int N = 1010;

int dp[N][N],f[N],a[N],mid,n,k;
int g[N<<1],ls[N<<1],nxt[N<<1],e;

inline void addedge(int x,int y)
{
    g[++e]=y; nxt[e]=ls[x]; ls[x]=e;
}

void dfs(int x,int fa)
{
    for(int i=1;i<=n;i++)
        dp[x][i]=(a[x]>=a[i]&&a[x]<=(LL)a[i]+mid)?0:INF;
    for(int i=ls[x];i;i=nxt[i])
    {
        int y=g[i];
        if (y==fa) continue;
        dfs(y,x);
        for(int j=1;j<=n;j++)
            dp[x][j]+=min(dp[y][j],f[y]+1);
    }
    for(int i=1;i<=n;i++)
        f[x]=min(f[x],dp[x][i]);
}

inline bool check()
{
    memset(f,INF,sizeof(f));
    dfs(1,0); return f[1]<=k;
}

int main()
{
    sf(n); sf(k);
    for(int i=1;i<=n;i++) sf(a[i]);
    for(int i=1;i<n;i++)
    {
        int x,y;
        sf(x); sf(y);
        addedge(x,y);
        addedge(y,x);
    }
    LL l=0,r=2e9,ans;
    while(l<=r)
    {
        mid=l+r>>1;
        if (check()) ans=mid,r=mid-1;
                        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}

覺得博主寫的好的話,打賞一下吧,互利互惠……