一道有機結合了計數和貪心這一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死