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

Wannafly挑戰賽28 Cmsc的寵物(二分答案+樹形dp)

題目連結

題意:

給你一棵n個節點的樹,每一個節點有一個權值,問你去掉至多k條邊之後,

任意兩個可以互相到達的點間的權值的差的最大值最小是多少。

解析:

這道題我一開始反著用貪心做,後來發現這道題根本不能從區域性最優得到全域性最優。

例如 

4 1
20 11 9 0
1 2
2 3
3 4

這組樣例k=1,k=2所刪的邊是完全不一樣的。

直接用dp也不行,因為dp求答案的過程不滿足樹自底向上的性質。

求一條邊能不能刪,關乎以這條邊兩個端點為根的子樹的差的最大值。

所以翻了題解,用二分答案+dp來做。

二分答案就是二分[0,max],max是原來樹中最大值和最小值的差。
 

首先二分出以一個答案mid後,我們驗證它就通過在差值最大為mid的條件下,

這棵樹最少能被分成幾個部分,即最少需要砍幾刀使得每一個部分的最大值-最小值的差值(下面簡稱極差)都<=mid

對於這個問題,就是用dp來解的。這個dp我感覺又奇怪,但又很巧妙。

我自己也不是完全理解它的原理以及是如何想到的。

dp[x][i]表示在以x為根的子樹(包括x)中,節點x所在部分的最小值是a[i]的條件下對於這棵子樹最少需要砍幾刀。

注意這個a[i]不一定是x的子樹中的節點!

那麼對於dp[x][i],如果a[x]-a[i]>mid||a[x]<a[i],那麼這個就是非法的情況,就把dp[x][i]=INF,否則賦值為0

那麼對於轉移方程,對於一個節點x,我們只需要考慮他的兒子節點y的dp值就可以了。

定義 f[y]=min(dp[y][j])  j=1...n

如果dp[y][i]<f[y]+1,那麼說明以y為根結點的子樹,最優解就是把y節點歸入最小值是a[i]的部分。

那麼直接把x加入到這個部分就可以了,所以dp[x][i]+=dp[y][i]

否則,說明y的子樹的最優解不是在y所在部分的最小值是a[i]的情況下,那麼x和y就是兩個部分的點,

對於不同的部分,我們就應該把這條邊砍掉dp[x][i]+=f[y]+1

最後在mid條件下的最小砍的數量就是f[1]

 

最後總結一下,這道題的dp按我理解就是從終態出發,每一個點最後肯定是在一個部分裡面的,並且這個部分也一定

是有最小值的。那麼我們就用dp去列舉這個狀態,然後這個dp用恰好滿足樹形的結構自底向上遞推,

一條邊的兩個端點在同一部分就不用刪,不在同一部分就刪除。然後二分也挺巧妙地,二分答案,

用最多刪除的邊的數量k去驗證答案。

 

最後我覺得奇怪的原因是,這個dp會把一些不存在的情況算出值

7 3
3 8 7 4 2 3 3
1 2
2 3
2 4
4 5
1 6
1 7

對於上面的情況最後dp[1][5]=2,但是我們畫出圖可以直到,1和5是根本不可能分到同一個部分的....

但是這些值好像又不會對正確答案產生影響.....我大概猜了一下,可能是切完2刀之後再把1的部分和5的部分

接起來,這樣1部分的最小值就是a[5],但是改切的刀又是一定會切的並不會影響dp值,切出來合在一起也不會

違反極差<=mid的性質。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll INF = 0X3F3F3F3F3F3F3F3F;
const int MAXN = 1e3+100;

ll a[MAXN];
ll mim;
int dp[MAXN][MAXN];
int f[MAXN];
int n,k;
int mp[MAXN][MAXN];

void dfs(int x,int fa)
{
    for(int i=1;i<=n;i++)
    {
        dp[x][i]=a[x]>=a[i]&&a[x]-a[i]<=mim?0:INF;
    }
    //dp[x][x]=0;
    for(int i=1;i<=n;i++)
    {
        if(i==fa) continue;
        if(mp[x][i])
        {
            int v=i;
            dfs(v,x);
            for(int j=1;j<=n;j++)
            {
                if(a[x]-a[j]<=mim&&a[x]>=a[j])
                {
                    dp[x][j]+=min(dp[v][j],f[v]+1);
                }
            }
        }
    }
    f[x]=INF;
    for(int i=1;i<=n;i++) f[x]=min(f[x],dp[x][i]);
}

bool check(ll x)
{
    mim=x;
    dfs(1,0);
    if(f[1]>k) return false;
    return true;
}


int main()
{
    scanf("%d%d",&n,&k);
    ll mx,mi;
    mx=-INF;
    mi=INF;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        mx=max(a[i],mx);
        mi=min(a[i],mi);
    }
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        mp[u][v]=mp[v][u]=1;
    }
    ll l=0;
    ll r=mx-mi;
    ll ans=INF;
    while(l<r)
    {
        ll mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid;
        else l=mid+1;
    }
    if(check(l)) ans=l;
    printf("%lld\n",ans);



}