看到题想了想,或许可以算每个子树,就像普通树形dp.
然后搞出了 dp [ i ] [ j ] ,g [ i ] [ j ] ,cnt [ i ] [ j ]...
并且我可以严谨的证明这玩意是错的。
然后移步题解,意识到树性DP除了可以按点统计,还可以按边来看。
考虑枚举到一条边 ( u , v ) ,对答案的贡献就是 black [ u ] * black [ v ] + white [ u ] * white [ v ] * w [ i ].z;
很自然,我们设 dp [ i ] [ j ] 为以 i 为根的子树内有 j 个黑节点的最大收益。
简单转移一下就好。
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cstring> 5 using namespace std; 6 const int N=2005; 7 typedef long long LL; 8 9 inline LL read(){ 10 LL x=0,f=1;char ch=getchar(); 11 while(ch<'0'||ch>'9'){ 12 if(ch=='-')f=-1; 13 ch=getchar(); 14 } 15 while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); 16 return x*f; 17 } 18 19 int n,kk; 20 LL x,y,z; 21 struct node{ 22 LL y,nxt,z; 23 }e[N<<1]; 24 int tot=0,h[N]; 25 26 inline void ad(LL x,LL y,LL z){ 27 ++tot; 28 e[tot].y=y;e[tot].z=z;e[tot].nxt=h[x];h[x]=tot; 29 } 30 31 LL dp[N][N]; 32 int sz[N]; 33 34 inline void dfs(int x,int f){ 35 sz[x]=1; 36 for(int i=h[x];i;i=e[i].nxt){ 37 int y=e[i].y; 38 if(y==f)continue; 39 dfs(y,x);sz[x]+=sz[y]; 40 } 41 } 42 43 inline void solve(int x,int f){ 44 memset(dp[x],-1,sizeof(dp[x]));dp[x][0]=dp[x][1]=0; 45 for(int i=h[x];i;i=e[i].nxt){ 46 int y=e[i].y; 47 if(y==f)continue; 48 solve(y,x); 49 for(int j=min(kk,sz[x]);j>=0;--j){ 50 for(int k=0;k<=min(j,sz[y]);++k){ 51 if(~dp[x][j-k]){ 52 LL val=((kk-k)*k+(n-sz[y]-(kk-k))*(sz[y]-k))*e[i].z; 53 dp[x][j]=max(dp[x][j],dp[y][k]+dp[x][j-k]+val); 54 } 55 } 56 } 57 } 58 } 59 int main(){ 60 n=read();kk=read(); 61 for(int i=1;i<n;++i){ 62 x=read();y=read();z=read(); 63 ad(x,y,z);ad(y,x,z); 64 } 65 dfs(1,1); 66 solve(1,1); 67 cout<<dp[1][kk]; 68 }
这里的初值为什么要设为-1?
因为转移是倒序的,所以第一次转移时可能发生dp[x][j-k]无值可传的现象,这样统计的答案就不是实际的值了。
做DP问题要关注状态是否真实,真实就是指合法的从已知状态转移过来。
转了假状态,会使答案变得fake。