状态定义错了,一头扎进去出不来了。。。
很显然状态应该是 (dp[N][K]) 这么开的(后面再多几维 (0/1) 也可能,但是这题不用)。有两种理解
很好想到的是,这个状态表示的是以 (u) 为根的子树内选了 (k) 个黑点的收益最大值
然后我发现这个状态没法转移
然后就开始一波乱搞尝试转移,比如在状态后面又开了 (2) 维,推完瞬间hack了自己。2h后,我去看了题解
状态应该这么定义:以 (u) 为根的子树中选 (k) 个黑点的贡献最大是多少
技不如人啊,这都没想到,以后看来要多换状态试试,不能死磕。
这里是 贡献 ,非常重要的转化
于是你发现你秒了这题(当然如果你没有尝试过上面那个错误的状态是很那立刻明白这有什么用的)
枚举根,枚举子树内取了几个黑点,不断合并子树即可
[dp[i][j+l]=max{dp[i][j]+dp[v][l]+l*(k-l)*w+(sz[v]-l)*(n-k-(sz[v]-l)) *w}
]
(w) 是这条边的边权,(sz) 是子树的大小。
转移前记得copy一份dp数组,或者倒序dp也行
就是对于每一条边,它的贡献就是:左边黑点数*右边黑点数*边权+左边白点数*右边白点数*边权
是不是很好理解?
但是洛谷出现了大面积被hack的情况。真不知道都这个年头了怎么还有人不算时间复杂度
原因:关于 (sz) ,这个问题很重要
如果就是上面那个方程,相信是个人都能看出来那是 (O(n^2k)) 的 但是稍微有点水平的OIer都知道那可以变成 (O(n^2))
但是正确的写法是动态更新 (sz)
即还没遍历子树时 (sz=1) ,每次转移完之后再加上那颗子树的大小。
这样就是 (O(n^2)) 了,因为每两个点只会在 (LCA) 处被计算一次
其实这个trick挺常见的(比如P4516 [JSOI2018]潜入行动)
(mathcal{Code:})
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
#define x first
#define y second
#define sz(v) (int)v.size()
#define pb(x) push_back(x)
#define mkp(x,y) make_pair(x,y)
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
while(isdigit(c))x=x*10+c-'0',c=getchar();
return f?x:-x;
}
#define N 2005
int n,k,siz[N];
LL dp[N][N],cpy[N];
int head[N],num_edge;
struct edge{int nxt,to,val;}e[N<<1];
void addedge(int fr,int to,int val){
++num_edge;
e[num_edge].nxt=head[fr];
e[num_edge].to=to;
e[num_edge].val=val;
head[fr]=num_edge;
}
void ckmax(LL&x,LL y){if(x<y)x=y;}
void dfs(int u,int ft){
siz[u]=1,dp[u][0]=dp[u][1]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;if(v==ft)continue;
dfs(v,u);
for(int j=0;j<=siz[u];++j)cpy[j]=dp[u][j];
for(int j=0,mx1=min(siz[u],k);j<=mx1;++j)
for(int l=0,mx2=min(siz[v],k-j);l<=mx2;++l)
ckmax(dp[u][j+l],cpy[j]+dp[v][l]+1ll*l*(k-l)*e[i].val+1ll*(siz[v]-l)*(n-k-(siz[v]-l))*e[i].val);
siz[u]+=siz[v];
}
}
signed main(){
n=read(),k=read();
for(int i=1;i<n;++i){
int x=read(),y=read(),z=read();
addedge(x,y,z),addedge(y,x,z);
}
memset(dp,-0x3f,sizeof(dp));
dfs(1,0);
printf("%lld
",dp[1][k]);
return 0;
}