一道有机结合了计数和贪心这一DP两大考点的神仙题,不得不说作法是很玄妙。html
首先咱们很容易想到DP,设\(f_{i,j}\)表示在以\(i\)为根节点的子树中选\(j\)个黑色节点的最大收益值。git
而后咱们考虑那种暴力转移就是那种看上去是\(O(n^3)\)实际经严格证实后时\(O(n^2)\)的DPspa
而后推推推推推推,一个小时过去仍是一个屁code
这个时候咱们不由质疑,这个鬼状态不会是错的吧。htm
没错,它就是错的,由于这样对于你子树上面的黑点节点之间的收益你都一无所知blog
而后咱们联想到另一道树上计数的题目:51Nod 1677 treecnt&&sol,而后咱们又是单独考虑每一条边的贡献。get
再仔细推一波能够发现一条边对于黑白点的贡献之和两边黑白点的个数有关,和具体的结构鸟关系都没有。string
因而咱们换一波方程,设\(f_{i,j}\)表示在以\(i\)为根节点的子树中选\(j\)个黑色节点对总答案的贡献it
而后咱们枚举子树中黑色点的数量而后一个相似于背包的转移便可。io
具体看CODE
#include<cstdio> #include<cctype> #include<cstring> using namespace std; const int N=2005; struct edge { int to,next,v; }e[N<<1]; int head[N],size[N],n,k,cnt,x,y,z,rt=1; long long f[N][N]; inline char tc(void) { static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline void read(int &x) { x=0; char ch; while (!isdigit(ch=tc())); while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); } inline void double_add(int x,int y,int z) { e[++cnt].to=y; e[cnt].next=head[x]; e[cnt].v=z; head[x]=cnt; e[++cnt].to=x; e[cnt].next=head[y]; e[cnt].v=z; head[y]=cnt; } inline void maxer(long long &x,long long y) { if (y>x) x=y; } inline int min(int a,int b) { return a<b?a:b; } inline void DFS(int now,int fa) { register int i,j,s,x; size[now]=1; f[now][0]=f[now][1]=0; for (i=head[now];~i;i=e[i].next) if (e[i].to!=fa) DFS(e[i].to,now),size[now]+=size[e[i].to]; for (i=head[now];~i;i=e[i].next) if (e[i].to!=fa) for (j=min(k,size[now]);j>=0;--j) { for (s=0,x=min(j,size[e[i].to]);s<=x;++s) maxer(f[now][j],f[e[i].to][s]+f[now][j-s]+1LL*e[i].v*(1LL*s*(k-s)+1LL*(size[e[i].to]-s)*(n-k-size[e[i].to]+s))); } } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); register int i; read(n); read(k); if (2*k>n) k=n-k; memset(head,-1,sizeof(head)); memset(f,167,sizeof(f)); for (i=1;i<n;++i) read(x),read(y),read(z),double_add(x,y,z); DFS(rt,-1); return printf("%lld",f[rt][k]),0; }
注意上面的一个小trick:
if (2*k>n) k=n-k;
这样对无关的常数浪费就会大大下降直接帮助我卡过了BZOJ的老爷机,不加T死