1. 程式人生 > >Codeforces 1088E 樹形dp+思維

Codeforces 1088E 樹形dp+思維

比賽的時候看到題意沒多想就放棄了。結果最後D也沒做出來,還掉分了,所以還是題目做的太少,人太菜。

回到正題:

題意:一棵樹,點帶權值,然後求k個子連通塊,使得k個連通塊內所有的點權值相加作為分子除以k的值最大,如果這樣的最大值有多個,就最大化k。

賽後看了看別人的程式碼仔細想了一想,還是挺容易的。

首先將樹分為若干個連通塊,考慮一個權值求和最大的連通塊,設該最大值為sum,那麼其他所有的連通塊,權值求和都不可能比當前的連通塊大。

在不考慮最大化k的情況下,就沒有必要再將其他的連通塊加進來(因為如果加進來,(sum+加進來的連通塊權值和)/(k+1)<=sum/k,就必定成立,其實本質上就是求一個均值,加比當前值還要小的值會使得這個均值變小),所以答案就是sum。

現在要考慮答案相同時,最大化k,那麼本質上只要將其他的權值和等同於當前最大權值和的連通塊加進來就好,這樣分子就變成了sum*k,k個連通塊。

經隊友賽後一提醒,其實實際上這就是一個最大連續和的樹上版本,樹形dp一下就出來了。

設dp[i]表示以i為根的子樹,包含i在內的權值求和最大的連通塊的權值和。

狀態轉移就有兩種選擇,列舉他的子樹,那麼:1.連這棵子樹;2.不連這顆子樹。

易得狀態轉移方程:

                    dp[i]+=max(dp[j],0) (j為i的子節點)(類似於線性結構上的最大連續和)

然後獲取所有dp中的最大值,就是sum。

最後再遍歷一遍dp陣列,每有一個dp值與sum相同,就k++,最後答案就是sum*k,k個連通塊。

 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int maxn=300005;
 5 int cnt;
 6 int head[maxn];
 7 struct edge
 8 {
 9     int to,nxt;
10 }e[maxn];
11 ll a[maxn];
12 void inline addedge(int u,int v)
13 {
14     e[++cnt].to=v;
15     e[cnt].nxt=head[u];
16 head[u]=cnt; 17 } 18 ll dp[maxn]; 19 ll ans=-1e18; 20 void dfs(int fa,int u) 21 { 22 dp[u]=a[u]; 23 for(int i=head[u];i;i=e[i].nxt) 24 { 25 int v=e[i].to; 26 if(v==fa) continue; 27 dfs(u,v); 28 dp[u]+=max(dp[v],0LL); 29 } 30 ans=max(ans,dp[u]); 31 } 32 int main() 33 { 34 #ifdef local 35 //freopen("in.txt","r",stdin); 36 #endif // local 37 int n; 38 cin>>n; 39 for(int i=1;i<=n;i++) 40 cin>>a[i]; 41 for(int i=1;i<=n-1;i++) 42 { 43 int x,y; 44 cin>>x>>y; 45 addedge(x,y); 46 addedge(y,x); 47 } 48 dfs(-1,1); 49 ll k=0; 50 for(int i=1;i<=n;i++) 51 if(dp[i]==ans) 52 k++; 53 cout<<ans*k<<" "<<k<<endl; 54 }
View Code