【題目描述】
有一棵點數爲N的樹,樹邊有邊權。給你一個在0~N以內的正整數K,你要在這棵樹中選擇K個點,將其染成黑色,並將其餘的N-K個點染成白色。將全部點染色後,你會得到黑點兩兩之間的距離加上白點兩兩之間距離的和的收益。問收益最大值是多少。
【輸入格式】
第一行兩個整數N,K。
接下來N-1行每行三個正整數fr,to,dis,表示該樹中存在一條長度爲dis的邊(fr,to)。輸入保證全部點之間是聯通的。
【輸出格式】
輸出一個正整數,表示收益的最大值。
【輸入樣例1】
3 1
1 2 1
1 3 2
【輸出樣例1】
3
【輸入樣例2】
5 2
1 2 3
1 5 1
2 3 1
2 4 2
【輸出樣例2】
17
【樣例解釋】
在第二個樣例中,將點1,2染黑就能得到最大收益。
【數據範圍】
對於30%的數據,N<=20
對於50%的數據,N<=100
對於100%的數據,N<=2000,0<=K<=N。 ide
很顯然是個樹dp,而後問題來了,怎麼設計狀態,根據經驗咱們能夠設計出$dp[i][j]$表示以$i$爲根的子樹中染了$j$個黑點的貢獻,可是這裏所說的貢獻是對總體仍是隻考慮局部呢,答案是對總體的貢獻,由於很顯然這個題目中知足局部最優並不必定就知足總體最優,這樣狀態定義就結束了,咱們在來考慮轉移,咱們設當前枚舉的根節點爲$x$,咱們再來枚舉他的子節點$y$,發現沒法直接經過子節點轉移到父節點,咱們再考慮,是什麼鏈接子節點和父節點,是邊,那咱們就能夠經過邊的貢獻來轉移,那麼每條邊的貢獻就是邊權×子樹中黑點數×子樹外黑點數+邊權×子樹中白點數×子樹外白點數。這樣就能夠愉快的轉移了spa
$dp[x][j+m]=\max{(dp[y][m]+dp[x][j]+val)}$設計
其中j枚舉x子樹大小,m枚舉y子樹大小,val就是邊的貢獻。code
而後就是要倒着枚舉(其實正着枚舉也行,就是麻煩),由於若是單純的正着枚舉,會致使用更新過的來更新,而不是正常的轉移,即你要用以前的子樹大小和當前子樹來更新父節點,但若是不作任何處理的正着枚舉就會致使用已經更新到size的值來更新,就會不對。blog
還有就是轉移要放到dfs循環裏邊,其實和上面同樣,就是你只是用前面的size,而不是總的size,具體看代碼裏的註釋吧get
還有要注意的一點就是由於這題你把全部黑點和白點互換不會對答案產生影響,因此$k=\min{(k,n-k)}$。io
一遍dfs便可。event
這樣作的複雜度時$O(n^2)$的,簡單點說就是由於每一個點最多隻會和其餘點乘一次。class
1 #include<cstdio> 2 using namespace std; 3 const int N=2005,M=4005,L=1<<15|1; 4 //#define int long long 5 #define rg register int 6 int first[N],nex[M],to[M],edge[M],v[N],size[N],tot,n,k;long long dp[N][N]; 7 char buf[L],*S,*T; 8 #define getchar() ((S==T&&(T=(S=buf)+fread(buf,1,L,stdin),S==T))?EOF:*S++) 9 inline int read(){ 10 rg ss=0;register char bb=getchar(); 11 while(bb<48||bb>57)bb=getchar(); 12 while(bb>=48&&bb<=57)ss=(ss<<1)+(ss<<3)+(bb^48),bb=getchar(); 13 return ss; 14 } 15 inline void add(rg a,rg b,rg c){ 16 to[++tot]=b,edge[tot]=c,nex[tot]=first[a],first[a]=tot; 17 } 18 inline int max(rg a,rg b){return a>b?a:b;} 19 inline int min(rg a,rg b){return a<b?a:b;} 20 inline long long Max(long long a,long long b){return a<b?b:a;} 21 void dfs(int x){ 22 int y; 23 size[x]=1;v[x]=1; 24 for(int i=first[x];i;i=nex[i]){ 25 if(v[y=to[i]]) continue; 26 dfs(y); 27 rg z=edge[i]; 28 rg qq=min(size[x],k),pp=min(size[y],k); 29 //倒序枚舉 30 for(rg j=qq;j>=0;--j)//這塊要放到裏面,緣由見blog 31 for(rg l=pp;l>=0;--l){ 32 long long del=1ll*z*1ll*l*1ll*(k-l)+1ll*z*1ll*(size[y]-l)*1ll*(n-k-(size[y]-l)); 33 dp[x][j+l]=Max(dp[x][j+l],dp[y][l]+dp[x][j]+del); 34 } 35 size[x]+=size[y]; 36 } 37 } 38 signed main(){ 39 n=read(),k=read(); 40 k=min(k,n-k); 41 for(rg i=1;i<n;++i){ 42 rg x=read(),y=read(),z=read(); 43 add(x,y,z); 44 add(y,x,z); 45 } 46 dfs(1); 47 printf("%lld",dp[1][k]); 48 }