题目链接:http://blog.youkuaiyun.com/liuqiyao_01/article/details/8477730
(在这谢谢dalao的整理)
常见树形dp
1.Hdu 1520 Anniversary party
题意:每个节点有权值,子节点和父节点不能同时选,问最后能选的最大价值是多少?
转移方程解释:
dp[root][0]+=max(dp[vec[root][j]][1],dp[vec[root][j]][0]);
//不要当前根节点,要子点大呢?还是不要子节点大呢?
dp[root][1]+=dp[vec[root][j]][0];
//要当前根节点,不要子节点的值
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=6050;
vector<int> vec[maxn];
int f[maxn];
int hap[maxn];
int dp[maxn][2];
void dfs(int root)
{
int len=vec[root].size();
dp[root][1]=hap[root];
for(int j=0;j<len;j++)
dfs(vec[root][j]);
for(int j=0;j<len;j++)
{
dp[root][0]+=max(dp[vec[root][j]][1],dp[vec[root][j]][0]);
dp[root][1]+=dp[vec[root][j]][0];
}
}
int main()
{
int n;
int a,b;
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&hap[i]);
vec[i].clear();
f[i]=-1;
dp[i][0]=dp[i][1]=0;
}
while(scanf("%d%d",&a,&b))
{
if(a==0&&b==0)break;
f[a]=b;
vec[b].push_back(a);
}
a=1;
while(f[a]!=-1) a=f[a];
dfs(a);
printf("%d\n",max(dp[a][1],dp[a][0]));
}
return 0;
}
2.Hdu 2196 Computer
题意: 经典题,求树每个点到其他点的最远距离.
解释: 首先转化为有根树,先处理当前节点到子节点的次长距离和最长距离,一次dfs,再处理当前节点从父节点走去的最长距离.二次dfs.
ps:为什么做次长距离?
可以在下图看到,绿色节点到子节点最长距离为2,父节点红色为3,可是当我dp绿色节点点时,发现父节点经过自己,那么这最长距离就不成立了,应该通过蓝色次长距离得到最长节点为3.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=10010;
struct Node
{
int to;
int next;
int len;
}edge[maxn*2];
int head[maxn];
int tol;
int dp1[maxn];//该节点到叶子节点最大的距离
int dp2[maxn];//次大距离
int maxid[maxn];//最大距离对应的序号
int smaxid[maxn];//次大的序号
void init()
{
tol=0;
memset(head,-1,sizeof(head));
}
void add(int a,int b,int len)
{
edge[tol].to=b;
edge[tol].len=len;
edge[tol].next=head[a];
head[a]=tol++;
edge[tol].to=a;
edge[tol].len=len;
edge[tol].next=head[b];
head[b]=tol++;
}
void dfs1(int u,int p)
{
dp1[u]=0;
dp2[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(v==p) continue;
dfs1(v,u);
if(dp2[u]<dp1[v]+edge[i].len)
{
dp2[u]=dp1[v]+edge[i].len;
smaxid[u]=v;
if(dp2[u]>dp1[u])
{
swap(dp2[u],dp1[u]);
swap(smaxid[u],maxid[u]);
}
}
}
}
void dfs2(int u,int p)
{
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(v==p)continue;
if(v==maxid[u])
{
if(edge[i].len+dp2[u]>dp2[v])
{
dp2[v]=edge[i].len+dp2[u];
smaxid[v]=u;
if(dp2[v]>dp1[v])
{
swap(dp2[v],dp1[v]);
swap(smaxid[v],maxid[v]);
}
}
}
else
{
if(edge[i].len+dp1[u]>dp2[v])
{
dp2[v]=edge[i].len+dp1[u];
smaxid[v]=u;
if(dp2[v]>dp1[v])
{
swap(dp2[v],dp1[v]);
swap(maxid[v],smaxid[v]);
}
}
}
dfs2(v,u);
}
}
int main()
{
int n;
int v,len;
while(scanf("%d",&n)!=EOF)
{
init();
for(int i=2;i<=n;i++)
{
scanf("%d%d",&v,&len);
add(i,v,len);
}
dfs1(1,-1);
dfs2(1,-1);
for(int i=1;i<=n;i++)
printf("%d\n",dp1[i]);
}
return 0;
}
删点或者删边类树形DP
1.Hdu 3586 Information Disturbing
题意:给定n个敌方据点,1为司令部,其他点各有一条边相连构成一棵树,每条边都有一个权值cost表示破坏这条边的费用,叶子节点为前线。现要切断前线和司令部的联系,每次切断边的费用不能超过上限limit,问切断所有前线与司令部联系所花费的总费用少于m时的最小limit
解释:首先上限我们是不知道的,但我们只知道切除当前边的最大上限值 limit,所以我们得二分,虽然可以二分枚举limit,可是我们发现题目中,切当前边符合<=limit的边,加起来的总和不超过m,所以在二分时,我们dp[i]存的值就是sum,若sum<=m,则r=mid.
对于dp方程
我们必须切每条边,我们可以这样处理,因为是棵树,我们可以把边权附到子节点的点权去.
然后dp[i]代表切除当前点的最小花费. 然后dp上来即可..
typedef long long ll;
const int INF=1000000+5;
const int maxn=1e5+10;
int val[maxn];
ll dp[maxn];
struct Node
{
int to;
int next;
} edge[maxn*2];
int tot;
int head[maxn];
int u,v,w;
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
edge[tot].to=u;
edge[tot].next=head[v];
head[v]=tot++;
}
int n,m;
void dfs(int u,int pre,int tt)
{
dp[u]=0;
bool flag=true;//判断是不是子节点.
for(int j=head[u]; j!=-1; j=edge[j].next)
{
int v=edge[j].to;
if(v==pre)continue;
flag=false;
dfs(v,u,tt);
dp[u]+=dp[v];
}
if(flag)
{
if(val[u]<=tt)dp[u]=val[u];
else dp[u]=INF;
//cout<<u<<" "<<dp[u]<<endl;
return ;
}
if(u!=1&&val[u]<=tt&&(val[u]<dp[u]))
{
dp[u]=val[u];
}
//cout<<u<<" "<<dp[u]<<endl;
}
int main()
{
while(scanf("%d%d",&n,&m),n+m)
{
init();
int lim=0;
for(int i=1; i<n; i++)
{
scanf("%d%d%d",&u,&v,&w);
lim=max(lim,w);
val[v]=w;
addedge(u,v);
}
int l=1;
int r=lim;
int ans=-1;
while(l<=r)
{
int mid=(l+r)/2;
dfs(1,-1,mid);
//cout<<"----->"<<endl;
if(dp[1]<=m)
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}
printf("%d\n",ans);
}
return 0;
}
2、Poj 3107 Godfather
题意:删点,使剩下的分支中最大的节点数最小.
解释:这道题求的是重心,树的重心是切除当前节点后,分开的每一块的最大数最小,就是树的重心了,所以对于每次先预处理子节点个个数num[v],然后dp[u]=min(dp[u],num[v]),即可知道切除了这个点之后,下面子节点的几块的大小,可是还有父节点的那块怎么办? 很简单啊.. dp[u]=min(dp[u],n-num[u])不就是上面那一块的大小了咯..这就求出来了。
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=5e4+10;
int n;
int u,v;
struct Node
{
int to;
int next;
}edge[maxn*2];
int tot;
int head[maxn];
void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
edge[tot].to=u;
edge[tot].next=head[v];
head[v]=tot++;
}
int top;
int dp[maxn];
int num[maxn];
int ans;
void dfs(int u,int pre)
{
dp[u]=0;
num[u]=1;
for(int j=head[u];j!=-1;j=edge[j].next)
{
int v=edge[j].to;
if(v==pre) continue;
dfs(v,u);
dp[u]=max(dp[u],num[v]);
num[u]+=num[v];
}
dp[u]=max(dp[u],n-num[u]);
if(dp[u]<ans)
{
ans=dp[u];
}
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
top=0;
ans=INF;
memset(head,-1,sizeof(head));
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
dfs(1,-1);
bool flag=true;
for(int i=1;i<=n;i++)
{
if(dp[i]==ans)
{
if(flag){printf("%d",i);flag=false;}
else printf(" %d",i);
}
}
printf("\n");
}
return 0;
}
3、Poj 2378 Tree Cutting
题意: 删点,使剩下的分支中有最大节点数的分支小等于总数一半,问有几种方案。
解释:同上.dp【u】已经是处理好的,暴力一下dp【u】即可.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=1e4+10;
int n;
int u,v;
int dp[maxn];
int num[maxn];
struct Node
{
int to;
int next;
}edge[maxn*2];
int tot;
int head[maxn];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
edge[tot].to=u;
edge[tot].next=head[v];
head[v]=tot++;
}
void dfs(int u,int pre)
{
dp[u]=0;
num[u]=1;
for(int j=head[u];j!=-1;j=edge[j].next)
{
int v=edge[j].to;
if(v==pre)continue;
dfs(v,u);
dp[u]=max(dp[u],num[v]);
num[u]+=num[v];
}
dp[u]=max(dp[u],n-num[u]);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
init();
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
dfs(1,-1);
bool flag=true;
for(int i=1;i<=n;i++)
{
if(dp[i]<=n/2){printf("%d\n",i);flag=false;}
}
if(flag)printf("NONE\n");
}
return 0;
}
4、Poj 1655 Balancing Act
题意:删点,使剩下的分支中最大的节点数最小.
解释:也同上.只是答案上的处理,dp[u]已经记录好了.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=20010;
int T;
int n;
int u,v;
vector<int> G[maxn];
void add(int u,int v)
{
G[u].push_back(v);
G[v].push_back(u);
}
int dp[maxn];//对于当前节点的最大的分块树;
int num[maxn];//对于当前节点的子树个数;
void dfs(int u,int pre)
{
dp[u]=0;
num[u]=1;
for(int j=0;j<G[u].size();j++)
{
int v=G[u][j];
if(v==pre)continue;
dfs(v,u);
dp[u]=max(dp[u],num[v]);
num[u]+=num[v];
}
dp[u]=max(dp[u],n-num[u]);
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=0;i<=n;i++)G[i].clear();
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v);
}
dfs(1,-1);
int ans=dp[1];
int ansid=1;
for(int i=1;i<=n;i++)
{
if(ans>dp[i])
{
ans=dp[i];
ansid=i;
}
}
printf("%d %d\n",ansid,ans);
}
return 0;
}
5、Poj 3140 Contestants Division
题意:删边,求删去某条边后两个分支的最小差异值
解释:做了上面的题之后其实都水到渠成了,这道也很水了,1次dfs就行了.因为通过n-num[u]就知道父节点信息.这就没什么好说了.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=1e5+10;
ll abs1(ll x)
{
if(x<0)return -x;
return x;
}
int n,m;
ll val[maxn];
ll dp[maxn];
struct Node
{
int to;
int next;
}edge[maxn*2];
int tot;
int u,v;
int head[maxn];
ll sum;
ll ans;
bool flag;
void init()
{
flag=true;
sum=0;
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
edge[tot].to=u;
edge[tot].next=head[v];
head[v]=tot++;
}
void dfs(int u,int pre)
{
dp[u]=val[u];
for(int j=head[u];j!=-1;j=edge[j].next)
{
int v=edge[j].to;
if(v==pre) continue;
dfs(v,u);
dp[u]+=dp[v];
}
if(flag)
{
ans=abs1(sum-2*dp[u]);
flag=false;
}
else
{
ans=min(ans,abs1(sum-2*dp[u]));
}
}
int main()
{
int Tcase=0;
while(scanf("%d%d",&n,&m),n+m)
{
Tcase++;
init();
for(int i=1;i<=n;i++)
{
scanf("%I64d",&val[i]);
sum+=val[i];
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
dfs(1,-1);
printf("Case %d: %I64d\n",Tcase,ans);
}
return 0;
}
(这属于常见形dp)6、Poj 1741 Tree(难)
题意:经典题,求树上两点间距离小等于K的方案数,树上分治。
解释:树分治,这道题比较难啊只有按上面顺序做完才可能对这题有点理解,我也不太说得清,可以参照kuangbin巨巨的代码,我也是半理解吧.其他的参考论文吧,我就做了一道难题...
const int MAXN = 10010;
const int INF = 0x3f3f3f3f;
struct Edge
{
int to,next,w;
}edge[MAXN*2];
int head[MAXN],tot;
void init()
{
tot = 0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)
{
edge[tot].to = v; edge[tot].w = w;
edge[tot].next = head[u];head[u] = tot++;
}
bool vis[MAXN];
int size[MAXN],dep[MAXN];
int le,ri;
int dfssize(int u,int pre)
{
size[u] = 1;
for(int i = head[u];i != -1;i = edge[i].next)
{
int v = edge[i].to;
if(v == pre || vis[v])continue;
size[u] += dfssize(v,u);
}
return size[u];
}
int minn;
//找重心
void getroot(int u,int pre,int totnum,int &root)
{
int maxx = totnum - size[u];
for(int i = head[u];i != -1;i = edge[i].next)
{
int v = edge[i].to;
if(v == pre || vis[v])continue;
getroot(v,u,totnum,root);
maxx = max(maxx,size[v]);
}
if(maxx < minn){minn = maxx; root = u;}
}
void dfsdepth(int u,int pre,int d)
{
dep[ri++] = d;
//cout<<"->"<<u<<endl;
for(int i = head[u];i != -1;i = edge[i].next)
{
int v = edge[i].to;
if(v == pre || vis[v])continue;
dfsdepth(v,u,d+edge[i].w);
}
}
int k;
int getdep(int a,int b)
{
sort(dep+a,dep+b);
int ret = 0, e = b-1;
for(int i = a;i < b;i++)
{
if(dep[i] > k)break;
while(e >= a && dep[e] + dep[i] > k)
{
e--;
}
//这个地方是个技巧,我们先预处理在子节点内所有 dep[e] + dep[i]<= k的情况
//然后再用把当前根节点的遍历一遍,减去这些情况,就得到跨当前根节点的个数了.
//cout<<"e:"<<e<<" "<<dep[e]<<"-----"<<"i:"<<i<<" "<<dep[i]<<endl;
ret += e - a + 1;
if(e >= i)ret--;
}
return ret>>1;
}
int solve(int u)
{
int totnum = dfssize(u,-1);
int ret = 0;
minn = INF;
int root;
getroot(u,-1,totnum,root);
// cout<<"root--->"<<root<<endl;
vis[root] = true;
for(int i = head[root];i != -1;i = edge[i].next)
{
int v = edge[i].to;
if(vis[v])continue;
ret += solve(v);
}
le = ri = 0;
for(int i = head[root];i != -1;i = edge[i].next)
{
int v = edge[i].to;
if(vis[v])continue;
dfsdepth(v,root,edge[i].w);
//cout<<"------------>"<<endl;
ret -= getdep(le,ri);
le = ri;
}
ret += getdep(0,ri);
//cout<<"------------>"<<ret<<endl;
for(int i = 0;i < ri;i++)
{
if(dep[i] <= k)ret++;
else break;
}
vis[root] = false;
return ret;
}
int main()
{
int n;
int u,v,w;
while(scanf("%d%d",&n,&k) == 2)
{
if(n == 0 && k == 0)break;
init();
for(int i = 1;i < n;i++)
{
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
memset(vis,false,sizeof(vis));
printf("%d\n",solve(1));
}
return 0;
}
树形背包问题(在树上进行分组背包处理)
引入kuangbin巨巨的分组背包思想:
状态转移,使用的“分组背包”思想。 使用一维数组的“分组背包”伪代码如下: for 所有的组i for v=V..0 for 所有的k属于组i f[v]=max{f[v],f[v-c[k]]+w[k]}
个人感想:
理解分组背包,整是开始分组背包的前提了.好了,我根据我做的题目我一个个列出来我初步的思想. (对于刚开始的时候我某些是借鉴别人的,之后就越做越顺了…)
1、Poj 1155 TELE
题意:某电台要广播一场比赛,该电台网络是由N个网点组成的一棵树,其中M个点为客户端,其余点为转发站。客户端i愿支付的钱为pay[i],每一条边需要的花费固定,问电台在保证不亏损的情况下,最多能使多少个客户端接收到信息?
解释:这道题我刚开始也是半懂的感觉,所以代码是抄人家的,因为我也不太会哦,只有理解了那个分组思想之后才能明白.
dp[i][j]:从当前u几点供给j个用户的获得的最大利润.
用上面的分组背包思想做就好了,莫非就是选和不选的问题,然后dp好了之后 遍历一下根节点哪一次dp[root][j]>=0的即可.. 就可以了.说得有点迷,看下代码就理解了.
#define MAXN 3010
struct Node{
int v,w,next;
}edge[MAXN*MAXN];
int dp[MAXN][MAXN];
int num[MAXN]; //容量
int head[MAXN];
int max(int a,int b)
{
return a>b?a:b;
}
void dfs(int u)
{
int i,j,k,v;
for(i=head[u];i!=-1;i=edge[i].next)
{
v=edge[i].v;
dfs(v);
num[u]+=num[v];
for(j=num[u];j>=1;j--) //背包 需要逆向
for(k=1;k<=num[v];k++)
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-edge[i].w);
}
}
int v,w;
int main()
{
int i,j,n,m,k,cnt=0;
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(i=1;i<=n-m;i++)
{
scanf("%d",&k);
while(k--)
{
scanf("%d%d",&v,&w);
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[i];
head[i]=cnt++;
}
}
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
dp[i][j]=-1e9;//因为状态有负值 所以不能为0
for(i=n-m+1;i<=n;i++)
{
num[i]=1;
scanf("%d",&dp[i][1]);
}
dfs(1);
for(i=num[1];i>=0;i--)
if(dp[1][i]>=0)
break;
printf("%d",i);
return 0;
}
2、Hdu 1011 Starship Troopers
题意:给出每个房间拥有的BUG数和能得到的能量数,然后给出每个房间的联通图,要到下一个房间必须攻破上一个房间,每个士兵最多消灭20个BUG,就算不足20个BUG也要安排一个士兵
解释:和上面分组背包,一毛一样,可以先预处理每个点的权值,然后再进行dp,而且这里有个坑点就是m==0时要特殊处理,注意下就行了
dp[u][j]:对于当前节点送j个士兵下去获得的最大权值.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=110;
/*
简单树形dp
dp[u][j]:在当前u节点,留下j个兵获去其他路(包括必定在这个节点)取得到的最大金额,
转移方程: dp[u][s]=max(dp[u][s],dp[u][s-j]+dp[v][j]);,留s-j个在u点获取的最大金额(包括走其他支),剩下j个兵走v获得的最大值.
*/
struct Node
{
int cost;
int val;
}room[maxn];
int dp[maxn][maxn];
vector<int> G[maxn];
int n,m;
bool vis[maxn];
void dfs(int u)
{
vis[u]=true;
memset(dp[u],0,sizeof(dp[u]));
if(!room[u].cost&& G[u].size()==1 && u!=1)room[u].cost=1;
for(int j=room[u].cost;j<=m;j++)
dp[u][j]=room[u].val;
for(int j=0;j<G[u].size();j++)
{
int v=G[u][j];
if(vis[v])continue;
dfs(v);
for(int s=m;s>=room[u].cost;s--)
{
for(int j=1;s-j>=room[u].cost;j++)
{
dp[u][s]=max(dp[u][s],dp[u][s-j]+dp[v][j]);
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==-1&&m==-1)break;
for(int i=1;i<=n;i++)G[i].clear();
int x;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x,&room[i].val);
room[i].cost=x/20+(x%20!=0);
}
for(int i=0;i<n-1;i++)
{
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
if(m==0)
{
printf("0\n");
continue;
}
memset(vis,0,sizeof(vis));
vis[1]=true;
dfs(1);
printf("%d\n",dp[1][m]);
}
return 0;
}
3、Poj 1947 Rebuilding Roads 求
题意:最少删除几条边使得子树节点个数为p.
解释:具体模型都和上面相似
dp[u][j]:代表当前节点,获得j个点的最少删边数.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=200;
int N,P;
vector<int> G[maxn];
int dp[maxn][maxn];
int pre[maxn];
void init()
{
for(int i=1;i<=N;i++)
{
G[i].clear();
pre[i]=i;
}
}
void getMap()
{
int a,b;
for(int i=1;i<N;i++)
{
scanf("%d%d",&a,&b);
G[a].push_back(b);
pre[b]=a;
}
}
void dfs(int u)
{
dp[u][1] = 0;//初始 自己不删边
for(int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
dfs(v);
for(int j = P; j >= 0; j--)
{
int t = dp[u][j] + 1;//直接删掉 与 子节点v 相连的边
for(int k = 0; k <= j; k++)
t = min(t, dp[u][j-k] + dp[v][k]);
dp[u][j] = t;
}
}
}
void solve()
{
int root;
for(int i=1;i<=N;i++)
{
if(pre[i]==i)
{
root=i;
break;
}
}
memset(dp,0x3f,sizeof(dp));
dfs(root);
int ans=INF;
for(int i=1;i<=N;i++)
{
if(i==root)
ans=min(ans,dp[i][P]);
else
ans=min(ans,dp[i][P]+1);
}
printf("%d\n", ans);
}
int main()
{
while(scanf("%d%d",&N,&P)!=EOF)
{
init();
getMap();
solve();
}
return 0;
}
4、Hdu 1561 The more, The Better
题意:在一棵树上选择若干个点获得的最大价值,选子节点必须先选父节点.
解释:这不就是刚刚派兵打bugs的题目吗..就是一毛一样啊..没什么好说的啊。。只是要多加一个虚拟节点.所以选点要到m+1链接成有根树就行了..很简单哦.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=205;
vector<int> G[maxn];
int N,M;
int v;
ll dp[maxn][maxn];
ll val[maxn];
void dfs(int u,int pre)
{
for(int j=0;j<G[u].size();j++)
{
int v=G[u][j];
if(v==pre) continue;
dfs(v,u);
for(int t=M+1;t>=1;t--)
{
for(int k=1;t-k>=1;k++)
{
dp[u][t]=max(dp[u][t],dp[u][t-k]+dp[v][k]);
}
}
}
}
int main()
{
while(scanf("%d%d",&N,&M),(N+M))
{
memset(dp,0,sizeof(dp));
for(int i=0;i<=N;i++)G[i].clear();
val[0]=0;
for(int i=1;i<=N;i++)
{
scanf("%d%I64d",&v,&val[i]);
G[i].push_back(v);
G[v].push_back(i);
for(int j=1;j<=M+1;j++)
{
dp[i][j]=val[i];
}
}
dfs(0,-1);
printf("%I64d\n",dp[0][M+1]);
}
return 0;
}
5、Hdu 4003 Find Metal Mineral (推荐,好题)
题意:树形DP+选且只能选一个物品的分组背包
解释:状态转移方程难想,这直接看kuangbin巨巨写的吧,他的分组思想也写在那
http://www.cnblogs.com/kuangbin/archive/2012/08/29/2661928.html
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=1e4+10;
int N,S,K;
struct Node
{
int to;
int next;
int w;
}edge[maxn*2];
int tot;
int head[maxn];
int u,v,w;
int dp[maxn][15];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
}
void addedge(int u,int v,int w)
{
edge[tot].to=v;
edge[tot].next=head[u];
edge[tot].w=w;
head[u]=tot++;
edge[tot].to=u;
edge[tot].next=head[v];
edge[tot].w=w;
head[v]=tot++;
}
void dfs(int u,int pre)
{
for(int j=head[u];j!=-1;j=edge[j].next)
{
int v=edge[j].to;
if(v==pre) continue;
dfs(v,u);
for(int k=K;k>=0;k--)
{
dp[u][k]+=(dp[v][0]+2*edge[j].w);
for(int t=1;t<=k;t++)
{
dp[u][k]=min(dp[u][k],dp[u][k-t]+dp[v][t]+t*edge[j].w);
}
}
}
}
int main()
{
while(scanf("%d%d%d",&N,&S,&K)!=EOF)
{
init();
for(int i=1;i<=N-1;i++)
{
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
dfs(S,-1);
printf("%d\n",dp[S][K]);
}
return 0;
}
6、Poj 2486 Apple Tree
题意:一颗树,n个点(1-n),n-1条边,每个点上有一个权值,求从1出发,走V步,最多能遍历到的权值
解释:看巨巨链接,写得太清楚...又容易理解.
http://blog.youkuaiyun.com/libin56842/article/details/10101807
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=100+10;
int N,K;
int val[maxn];
struct Node
{
int to;
int next;
}edge[maxn*2];
int tot;
int head[maxn];
int dp[maxn][205][2];
int u,v;
void init()
{
tot=0;
memset(dp,0,sizeof(dp));
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
edge[tot].to=u;
edge[tot].next=head[v];
head[v]=tot++;
}
void dfs(int u,int pre)
{
for(int j=head[u];j!=-1;j=edge[j].next)
{
int v=edge[j].to;
if(v==pre) continue;
dfs(v,u);
for(int k=K;k>=0;k--)
{
for(int t=1;t<=k;t++)
{
dp[u][k][1]=max(dp[u][k][1],dp[u][k-t][0]+dp[v][t-1][1]);
if(t-2>=0)
{
dp[u][k][0]=max(dp[u][k][0],dp[u][k-t][0]+dp[v][t-2][0]);
dp[u][k][1]=max(dp[u][k][1],dp[u][k-t][1]+dp[v][t-2][0]);
}
}
}
}
}
int main()
{
while(scanf("%d%d",&N,&K)!=EOF)
{
init();
for(int i=1;i<=N;i++)
{
scanf("%d",&val[i]);
for(int j=0;j<=K;j++){dp[i][j][0]=dp[i][j][1]=val[i];}
}
for(int i=1;i<N;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
dfs(1,-1);
printf("%d\n",max(dp[1][K][0],dp[1][K][1]));
}
return 0;
}
7、Poj 3345 Bribing FIPA
题意:现在有n个村子,你想要用收买m个村子为你投票,其中收买第i个村子的代价是val[i]。但是有些村子存在从属关系,如果B从属于A国,则收买了A也意味着买通了B,而且这些关系是传递的。问你最小要付出的代价是多少?
解释:这道题其实很简单,不过稍微有点难理解
我是借鉴了
http://blog.sina.com.cn/s/blog_6635898a0100qrdl.html 大牛的博客,不过dp方程我是自己写的,因为有点懒这道题目的输入好麻烦哦,我就不想写那么多,我就写dp方程,而且我对巨巨的dp方程也有点不赞同,我的思想是相反的,就是dp[i][j] 代表在当前获取j张个投票权的所需要的最小花费.我写的方程和巨巨不一样,看哪个好理解咯.
const int MAX = 205;
const int INF = 99999999;
struct{
int v, nxt;
}edge[MAX];
int k, edgeHead[MAX];
int n, m, mm;
char name[MAX][105];
int num[MAX], val[MAX];
int tmp[MAX], dp[MAX][MAX];
bool rt[MAX];
void addEdge(int u, int v){
edge[k].v = v;
edge[k].nxt = edgeHead[u];
edgeHead[u] = k ++;
}
void dfs(int u){
int i, j, k, v;
if(u!=0)num[u] = 1;
else num[u]=0;
dp[u][1] = val[u];
for(i = edgeHead[u]; i; i = edge[i].nxt)
{
v = edge[i].v;
dfs(v);
num[u] += num[v];
for(int j=num[u];j>=0;j--)
{
dp[u][j]=min(dp[u][j],val[u]);
for(int t=0;t<=num[v]&&(j+t)<=num[u];t++)
{
dp[u][j+t]=min(dp[u][j+t],dp[u][j]+dp[v][t]);
}
}
}
}
int search(char *str){
for(int i = 1; i <= mm; i ++)
if(!strcmp(str, name[i]))
return i;
strcpy(name[mm ++], str);
return mm-1;
}
int main()
{
int i, j, u, v, va;
char str[105];
while(gets(str) && str[0] != '#'){
sscanf(str, "%d%d", &n, &m); // sscanf()函数应用在这里很适合:把str当为输入流。
memset(name, 0, sizeof(name));
for(i = 0; i <= n; i ++){
for(j = 1; j <= n; j ++)
dp[i][j] = INF;
dp[i][0] = 0;
edgeHead[i] = 0;
rt[i] = true;
}
mm = 1, k = 1;
for(i = 1; i <= n; i ++){
scanf("%s%d", str, &va);
u = search(str);
val[u] = va;
while(getchar() != '\n'){
scanf("%s", str);
v = search(str);
rt[v] = false;
addEdge(u, v);
}
}
val[0] = INF;
for(i = 1; i <= n; i ++) // 加入总根。
if(rt[i])
addEdge(0, i);
dfs(0);
printf("%d\n", dp[0][m]);
}
return 0;
}
总结
其实树形dp真的很强大,这不是简简单单可以像数位dp一样,套个模板即可…要真正理解还是得花心思的…慢慢来,脚踏实地的做,理解好了比刷题数多更重要…