WannaFly挑戰賽 28 C msc的寵物(二分答案 + 樹形dp)
阿新 • • 發佈:2018-12-20
題意相當於是把一棵樹分為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; }
覺得博主寫的好的話,打賞一下吧,互利互惠……