[bzoj1812][Ioi2005]riv
树dp还是一如既往的烂呢。普通树转成左二子右兄弟树还是第一次用,思想完全没变吧,就是好写了。
题意就是给一棵树,树的点上有物品,边有长度,物品只能往祖先送,要修建$k$个接收的地方。令代价最小。(日了狗了沉船之后说话都说不清楚。还是看原题吧)$n \leq 100 , k \leq 50$//upd:后来出货了
令$f[i][j][k]$表示$i$的子树及其左侧所有兄弟的子树里,修建$j$个伐木场,并且$i$最近一个修建伐木场的祖先为$k$,最小代价。
分两种情况转移:
1.在$i$处修建伐木场,那么可以从$f[son][x][i]+f[bro][j-x-1][k]$转移过来。因为在这里修建不能影响兄弟,会影响儿子。
2.不在$i$处修建伐木场,那么可以从$f[son][x][k]+f[bro][j-x][k]+w[i] \times dis(i,k)$转移过来。
主要还是利用树形背包的思想。左儿子右兄弟确实使代码简介许多。
#include<bits/stdc++.h> using namespace std; const int N=101; inline int read(){ int r=0,c=getchar(); while(!isdigit(c))c=getchar(); while(isdigit(c)) r=r*10+c-'0',c=getchar(); return r; } struct Edge{ int to,nxt,w; }e[N*2]; int head[N],cnt=1; int ls[N],rs[N],d[N]; void add(int u,int v,int w){ rs[v]=e[head[u]].to; e[cnt]=(Edge){v,head[u],w}; head[u]=cnt++; } void getdep(int u,int dep){ d[u]=dep; for(int i=head[u];i;i=e[i].nxt) getdep(e[i].to,dep+e[i].w); } int n,k; int w[N],f[N][N][N]; int dp(int u,int x,int v){ if(!~u)return 0; if(~f[u][x][v])return f[u][x][v]; f[u][x][v]=2e9; int tmp=w[u]*(d[u]-d[v]); for(int i=0;i<=x;i++) f[u][x][v]=min(f[u][x][v],dp(ls[u],i,v)+dp(rs[u],x-i,v)+tmp); for(int i=0;i<x;i++) f[u][x][v]=min(f[u][x][v],dp(ls[u],i,u)+dp(rs[u],x-i-1,v)); return f[u][x][v]; } void init(){ memset(f,-1,sizeof f); e[0].to=-1;rs[0]=-1; n=read(),k=read(); for(int i=1;i<=n;i++){ w[i]=read(); int v=read(),w=read(); add(v,i,w); } for(int i=0;i<=n;i++) ls[i]=e[head[i]].to; } void solve(){ printf("%d\n",dp(0,k,0)); } int main(){ init(); getdep(0,0); solve(); } /* 4 2 1 0 1 1 1 10 10 2 5 1 2 3 */