題目鏈接:http://blog.csdn.net/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.csdn.net/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一樣,套個模板即可…要真正理解還是得花心思的..慢慢來,腳踏實地的做,理解好了比刷題數多更重要..