hdu 1561 The more, The Better 树形DP入门 利用分组背包的思想
#include
#include
#include
using namespace std;
int dp[250][250];
vector edge[250];
int n,m;
int val[250];
int max(int a,int b){
return a>b?a:b;
}
/*
/////////////////////////////////////////////
分组背包:
for 所有的组i
for v=V..0
for 所有的k属于组i
f[v]=max{f[v],f[v-c[k]]+w[k]}
*/
void dfs(int u,int fa){//用分组背包的思想更易于理解以及掌握
int i,j,k;
dp[u][1]=val[u];
for(i=0;i=1;k--)//枚举背包容量,dp[v][1....k]的状态(已经得出的最优状态)为v儿子这组背包的k个物品
{
for(j=1;j<=k-t;j++)//枚举第i组的每个物品,即第i个儿子的所有状态
{
//u为根节点时子节点可得到的最大分配为枚举的背包容量,否则要减1
if(dp[u][k-j]!=-1&&dp[v][j]!=-1)
dp[u][k]=max(dp[u][k],dp[u][k-j]+dp[v][j]);//枚举每个物品,找到能让dp[u][k]更优的物品
/*
注意这里的讲究:
j递增,k-j递减,所以保证了每次如果dp[u][k]被更新的话dp[u][k-j]的状态都是原来的状态(没有被v更新过),
也保证了dp[u][k]是最多取一个物品来更新的,*_* 哈哈,终于把每个细节搞通了,ORZ myself
*/
}
//其实就相当于用k-1的容量(u为0时是k)去分配给所有的儿子,得出最优的分配方案,再更新dp[u][k];
}
}
}
int main()
{
int i,a,b;
while(scanf("%d%d",&n,&m),(n||m))
{
for(i=0;i<=n;i++) edge[i].clear();
val[0]=0;
for(i=1;i<=n;i++)
{
scanf("%d%d",&a,&b);
edge[a].push_back(i);
val[i]=b;
}
memset(dp,-1,sizeof(dp));dp[0][0]=0;
dfs(0,-1);
printf("%d\n",dp[0][m]);
}
return 0;
}
另一种写法
#include#include #include using namespace std; int dp[250][250]; vector<int> edge[250]; int n,m; int val[250]; int max(int a,int b){ return a>b?a:b; } /* ///////////////////////////////////////////// 分组背包: for 所有的组i for v=V..0 for 所有的k属于组i f[v]=max{f[v],f[v-c[k]]+w[k]} */ void dfs(int u,int fa){//用分组背包的思想更易于理解以及掌握 int i,j,k; dp[u][1]=val[u]; for(i=0;i //枚举每一组背包,即每一个儿子,每组背包至少去一个物品 { int v=edge[u][i]; if(v==fa) continue; dfs(v,u); int t=1; if(u==0) t=0; for(k=m;k>=1;k--) { for(j=t;j<=k-1;j++)//也相当于是枚举第i组背包的每个物品 { if(dp[u][j]!=-1&&dp[v][k-j]!=-1) dp[u][k]=max(dp[u][k],dp[u][j]+dp[v][k-j]);//枚举每个物品,找到能让dp[u][k]更优的物品 } } } } int main() { int i,a,b; while(scanf("%d%d",&n,&m),(n||m)) { for(i=0;i<=n;i++) edge[i].clear(); val[0]=0; for(i=1;i<=n;i++) { scanf("%d%d",&a,&b); edge[a].push_back(i); val[i]=b; } memset(dp,-1,sizeof(dp));dp[0][0]=0; dfs(0,-1); printf("%d\n",dp[0][m]); } return 0; }
hdu 4003 Find Metal Mineral
/*
还是分组背包的思想,与上一题差不多
不过这个题目有一个特点,花费的是边权
所以每次分配背包容量时,都要加上相应的边权消耗
dp[i][j]表示以i为根的子树,放j个机器人去遍历(不回来)的最小花费
dp[i][0]就表示没有机器人停留在子树中,即派了一个机器人下去,
遍历完后又回来了,其实就等于子树中边权值和的两倍
*/
#include
#include
#include
using namespace std;
vector > edge[10010];
int dp[10010][15];
int m;
int min(int a,int b){return a>b?b:a;}
void dfs(int u,int fa){
int i,j,k;
for(i=0;i=0;k--){
dp[u][k]+=dp[v][0]+2*edge[u][i].second;//dp[v][0]表示遍历v子树后回来,即没有机器人停留在子树中
for(j=1;j<=k;j++)
dp[u][k]=min(dp[u][k],dp[u][k-j]+dp[v][j]+j*edge[u][i].second);//派j个机器人给v子树就会经过u-v之间的边j次
}
}
}
int main(){
int n,s,i,a,b,w;
while(scanf("%d%d%d",&n,&s,&m)!=EOF){
for(i=0;i<=n;i++) edge[i].clear();
for(i=0;i
poj 1849 Two 类似于上面一题,m=2
#include
#include
#include
using namespace std;
vectorint,int> > edge[10010];
int dp[10010][15];
int m;
int min(int a,int b){return a>b?b:a;}
void dfs(int u,int fa){
int i,j,k;
for(i=0;iint v=edge[u][i].first;
if(v==fa) continue;
dfs(v,u);
for(k=m;k>=0;k--){
dp[u][k]+=dp[v][0]+2*edge[u][i].second;
for(j=1;j<=k;j++)
dp[u][k]=min(dp[u][k],dp[u][k-j]+dp[v][j]+j*edge[u][i].second);
}
}
}
int main(){
int n,s,i,a,b,w;
while(scanf("%d%d",&n,&s)!=EOF){
m=2;
for(i=0;i<=n;i++) edge[i].clear();
for(i=0;i1;i++){
scanf("%d%d%d",&a,&b,&w);
edge[a].push_back(make_pair(b,w));
edge[b].push_back(make_pair(a,w));
}
memset(dp,0,sizeof(dp));
dfs(s,0);
printf("%d\n",dp[s][m]);
}
}
http://www.codeforces.com/problemset/problem/161/D
判断树中有多少的长度为k的简单路径dp[i][j]记录以i为根长度为j的路径数(i为路径的端点) ,每次回溯上来的时候维护dp[i][1--k]的值就好了
#include#include #include using namespace std; typedef __int64 lld; lld dp[50010][510]; lld ans; vector<int> g[50010]; int k; void dfs(int u,int fa) { int i,j; dp[u][0]=1; for(i=0;i ) { int v=g[u][i]; if(v==fa) continue; dfs(v,u); for(j=1;j<=k;j++) ans+=dp[u][j-1]*dp[v][k-j]; for(j=1;j<=k;j++) dp[u][j]+=dp[v][j-1]; } } int main() { int n,i,j,a,b; scanf("%d%d",&n,&k); for(i=0;i 1;i++) { scanf("%d%d",&a,&b); g[a].push_back(b); g[b].push_back(a); } memset(dp,0,sizeof(dp)); ans=0; dfs(1,0); printf("%I64d\n",ans); return 0; }
hdu 3586 Information Disturbing
//去掉一些边使得根节点不与叶子节点连通,在去掉的总的边权<=m的前提下 //使得所有边中最大的边权尽可能的小 #include#include const int INF = 100000; const int maxn = 1010; struct node{ int v,w,next; }edge[100010]; int head[maxn]; int tot; void add(int u,int v,int w) { edge[tot].v=v; edge[tot].w=w; edge[tot].next=head[u]; head[u]=tot++; } //二分判可行 //dfs 求出 子树中去掉的边权之和,如果大于u->v之间的边权,则改为去u->之间的边 //然后一层层回溯上去就好了 int dfs(int u,int fa,int num) { int sum=0; bool flag=false; for(int i=head[u];i!=-1;i=edge[i].next) { if(edge[i].v==fa) continue; int s=dfs(edge[i].v,u,num); if(s>edge[i].w&&edge[i].w<=num) { s=edge[i].w; } sum+=s; flag=true; } if(!flag) return INF; return sum; } int main() { int n,m,i,j,a,b,c; while(scanf("%d%d",&n,&m),(n||m)) { tot=0; memset(head,-1,sizeof(head)); for(i=0;i 1;i++) { scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } int l=1,r=1001; int ans=-1; while(l<=r) { int mid=(l+r)>>1; if(dfs(1,1,mid)<=m) { ans=mid; r=mid-1; } else l=mid+1; } printf("%d\n",ans); } }
zoj 3506 cut the tree
/* 题意:给出一颗树,每个节点都有一个权值,每次可以砍掉一条边,然后你可以在分开的两个集合中选一个, 求恰好砍m次能获得的最大权值和最小权值 这题的关键在于细节处理,+1,-1都要很小心 di[i]表示以i为根的子树的边的数量 dp[i][j]代表以i为根的子树砍j刀所能获得的最小权值,最大权值的话只需将所有点的权值变成负的再dfs即可 求完dp后,要进行简单的转换才是答案 假设最后剩下的节点属于i为根的子树,则i与父节点之间的边肯定是被砍掉的 用dp[i][j]来更新答案时,需要先算出j的范围,即以i为根的子树至少需要砍几刀,最多能够砍几刀 树边有n-1条,以i为根的子树最多有di【i]条边,总共要砍m条边 所以i子树外的其他边最多能够砍 n-1-di[i] 条,所以i子树至少要砍m-(n-1-di[i])条,小于0时就是0了 最多能砍min(di[i],m-1)条,因为i与父节点之间的边被砍掉了 */ #include#include #include using namespace std; const int INF = 100000000; const int maxn = 1010; int dp[maxn][30]; vector<int> g[maxn]; int val[maxn]; int di[maxn]; int n, m; int min(int a, int b) { return a < b ? a : b; } int max(int a, int b) { return a > b ? a : b; } void dfs(int u, int fa) { int i, j, k; dp[u][0] = val[u];//在第一次遍历到u的时候先记录u节点的权值,待回溯的时候再加上 di[u] = 0; for (i = 1; i <= m; i++) dp[u][i] = INF; for (i = 0; i < (int) g[u].size(); i++) { int v = g[u][i]; if (v == fa) continue; dfs(v, u); di[u] += di[v] + 1; for (k = m; k >= 0; k--) { dp[u][k] += dp[v][0];//一刀都不砍 for (j = 1; j <= k; j++)//保留u->v之间的边 dp[u][k] = min(dp[u][k], dp[v][j] + dp[u][k - j]); for (j = 1; j <= di[v] + 1 && j <= k; j++)//砍掉u->v之间的边 dp[u][k] = min(dp[u][k], dp[u][k - j]); } } } int solve() { dfs(1, 0); int ans = dp[1][m]; for (int i = 2; i <= n; i++) { for (int j = max(0, m - n + di[i]+1); j <= di[i] && j < m; j++)//i与父节点之间的边一定砍掉(j ans = min(ans, dp[i][j]);//i子树的外面 至多 能砍 (n-1-di[i])刀,其他的刀必须要在i子树内砍 } return ans; } int main() { int i, j, a, b; while (scanf("%d%d", &n, &m) != EOF) { for (i = 1; i <= n; i++) g[i].clear(); for (i = 1; i <= n; i++) scanf("%d", &val[i]); for (i = 1; i <= n - 1; i++) { scanf("%d%d", &a, &b); g[a].push_back(b); g[b].push_back(a); } printf("%d ", solve()); for (i = 1; i <= n; i++) val[i] = -val[i]; printf("%d\n", -solve()); } }
poj 1155 TELE
/* DP[i][j]表示以i为根节点的子树总共使j个客户收到信息的最大报酬 dp[u][j+k]=max(dp[u][j+k],dp[u][j]+dp[v][k]-w) u是v的父节点,w为u、v的边权 */ #include#include #define max(a,b) a>b?a:b const int INF = 1000000000; const int maxn = 3010; int head[maxn],dp[maxn][maxn],n,m; int num[maxn]; struct EDGE{ int v,w,next; }edge[maxn]; int tot; void add_edge(int s,int t,int w){ edge[tot].v=t; edge[tot].w=w; edge[tot].next=head[s]; head[s]=tot++; } void dfs(int u,int fa){ int i,j,k,tmp[maxn]; for(i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(v==fa) continue; dfs(v,u); for(j=0;j<=num[u];j++) tmp[j]=dp[u][j];//先预存下所有的最优值,因为在下面的更新过程中有可能会被更新掉 for(j = 0; j <= num[u]; j ++) for(k = 1; k <= num[v]; k ++) dp[u][j+k] = max(dp[u][j+k], tmp[j]+dp[v][k]-edge[i].w); num[u] += num[v];//利用回溯,自底向上的进行DP } } int main(){ int i,j,k,a,c; tot=0; memset(head,-1,sizeof(head)); scanf("%d%d",&n,&m); for(i=1;i<=n;i++) for(j=1;j<=n;j++) dp[i][j]=-INF; for(i=1;i<=n-m;i++){ scanf("%d",&k); for(j=0;j ){ scanf("%d%d",&a,&c); add_edge(i,a,c); } } memset(num,0,sizeof(num)); for(i=n-m+1;i<=n;i++){ num[i]=1; scanf("%d",&dp[i][1]); } dfs(1,0); for(i=m;i>=0;i--){ if(dp[1][i]>=0){//输出最多的可能的用户数,在不亏本的前提下 printf("%d\n",i); break; } } return 0; }
poj 3107
/* 求一棵树分别去掉哪些点后可以使得剩下的点集中点数最多的连通块的点数最小 直接进行一次DFS求解即可 */ #include#include const int maxn = 50010; struct node{ int v,next; }edge[maxn*2]; int head[maxn]; int E,ans,n; void add(int a,int b) { edge[E].v=b; edge[E].next=head[a]; head[a]=E++; } inline int max(int a,int b){ return a>b?a:b; } inline int min(int a,int b){ return aa:b; } int res[maxn]; int dfs(int u,int fa) { int i,Max=0; int s=1; for(i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(v==fa) continue; int t=dfs(v,u); Max=max(Max,t); s+=t; } Max=max(Max,n-s); res[u]=Max; ans=min(ans,Max); return s; } int main() { int i,j,a,b; while(scanf("%d",&n)!=EOF) { E=0;ans=100000000; for(i=1;i<=n;i++) head[i]=-1; for(i=1;i ) { scanf("%d%d",&a,&b); add(a,b); add(b,a); } dfs(1,0); for(i=1;i<=n;i++) { if(res[i]==ans) printf("%d ",i); } puts(""); } return 0; }
poj 1655
#include#include #include using namespace std; vector<int> edge[20010]; int son[20010]; int ans[20010]; int max(int a,int b){ return a>b?a:b; } void dfs(int u,int fa) { son[u]=1; ans[u]=1; for(int i=0;i ) { int to=edge[u][i]; if(to==fa) continue; dfs(to,u); son[u]+=son[to]; ans[u]=max(ans[u],son[to]); } } int main() { int t,n,i,j,a,b; scanf("%d",&t); while(t--) { scanf("%d",&n); for(i=0;i<=n;i++) edge[i].clear(); for(i=1;i ) { scanf("%d%d",&a,&b); edge[a].push_back(b); edge[b].push_back(a); } dfs(1,0); int ret=~0u>>1; int id; for(i=1;i<=n;i++) { if(max(ans[i],n-son[i])<ret) { ret=max(ans[i],n-son[i]); id=i; } } printf("%d %d\n",id,ret); } }
poj 1741 树的分治算法 买一送一(poj 1987)
/* 详见09年国家队论文 《分治算法在树的路径问题中的应用》 每次先找到重心,可以用反证法证明以重心为根的子树中的节点数不超过n/2 所以每次在子树中重新找重心的时候都把问题的规模缩小了1/2即分治了, 这样子树的深度就不会超过log(n) */ #include#include #include using namespace std; const int maxn = 10010; bool used[maxn]; int head[maxn], son[maxn] , d[maxn] , ans[maxn]; struct node { int v,w,next; }edge[2*maxn]; int tot,Max,root,cnt,m,n; void add(int a,int b,int w) { edge[tot].v=b; edge[tot].w=w; edge[tot].next=head[a]; head[a]=tot++; } void init() { memset(used,false,sizeof(used)); memset(head,-1,sizeof(head)); int a,b,w;tot=0; for(int i=1;i ) { scanf("%d%d%d",&a,&b,&w); add(a,b,w); add(b,a,w); } } void get_root(int u,int fa,int size)//poj 1695 实现的功能 找重心 { int mx=-1; son[u]=1; for(int i=head[u],to;i!=-1;i=edge[i].next) { to=edge[i].v; if(used[to] || to==fa) continue; get_root(to,u,size); son[u]+=son[to]; if(son[to]>mx) mx=son[to]; } if(size-son[u]>mx) mx=size-son[u]; if(mx u; } void get_dis(int u,int len,int fa) { d[++cnt]=len; for(int i=head[u],to;i!=-1;i=edge[i].next) { to=edge[i].v; if(used[to] || to==fa) continue; get_dis(to,len+edge[i].w,u); } } int calc(int u,int len)//nlog(n) { cnt=0; int sum=0; get_dis(u,len,-1); sort(d+1,d+cnt+1); for(int i=1,j=cnt;i<=j;i++) { while(d[i]+d[j]>m && i<=j) j--; if(i i; } return sum; } int find_root(int u) { Max=son[u]; get_root(u,0,Max); return root; } void solve(int u)//可以证明搜索树的深度上界为log(n) { u=find_root(u); ans[u]=calc(u,0); used[u]=true; for(int i=head[u],to;i!=-1;i=edge[i].next) { to=edge[i].v; if(used[to]) continue; ans[u]-=calc(to,edge[i].w); solve(to); } } int main() { while(scanf("%d%d",&n,&m)!=EOF && n) { init(); son[1]=n; solve(1); int res=0; for(int i=1;i<=n;i++) res+=ans[i]; printf("%d\n",res); } return 0; }
poj 2378
#include#include #include using namespace std; const int maxn = 10010; vector<int> edge[maxn]; int sum[maxn],ans[maxn],n; void dfs(int u,int fa){ sum[u]=1; int i,j; bool flag=true; for(i=0;i ){ int to=edge[u][i]; if(to==fa) continue ; dfs(to,u); if(sum[to]>n/2) flag=false; sum[u]+=sum[to]; } if(flag&&n-sum[u]<=n/2) ans[u]=true; } int main(){ int i,a,b; while(scanf("%d",&n)!=EOF){ memset(ans,0,sizeof(ans)); for(i=0;i<=n;i++) edge[i].clear(); for(i=0;i 1;i++){ scanf("%d%d",&a,&b); edge[a].push_back(b); edge[b].push_back(a); } dfs(1,0); for(i=1;i<=n;i++){ if(ans[i]) printf("%d\n",i); } puts(""); } return 0; }
二分+树形DP+状态压缩 http://www.bnuoj.com/bnuoj/problem_show.php?pid=17189
/* 给出一棵树,每个节点都有一个质量,一种颜色, 总共有7种颜色,每条边都有一个花费v,砍断一条边可以获得该子树所有的节点 但是要花费v的能量,总能量为tot 最后问你在收集满所有的颜色种类的前提下 能获得的最大的质量的果子是多少(所有获得的果子中最小的质量) 二分答案,判断通过质量大于等于mid的所有节点能否完成目标 dp[i][j]表示i子树颜色状态为j时的最小花费 sum[i]表示i子树总的颜色状态 */ #include#include #include #include using namespace std; const int inf = 0x3f3f3f3f; const int M = 110; int dp[M][(1<<7)+1]; int sum[M],col[M],cut[M],qual[M]; vector<int> edge[M]; void Min(int &a,int b){ if(bb; } void dfs(int u,int fa,int mid) { memset(dp[u],0x3f,sizeof(dp[u])); dp[u][0]=0;sum[u]=0; for(int i=0;i ) { int v=edge[u][i]; if(v==fa) continue; dfs(v,u,mid); sum[u]|=sum[v]; for(int j=0;j<(1<<7);j++) { if(dp[u][j]==inf) continue; for(int k=0;k<(1<<7);k++) Min(dp[u][j|k],dp[u][j]+dp[v][k]); } } if(qual[u]>=mid) sum[u]|=(1< //如果u的质量符合要求,把该颜色并入sum【u】 Min(dp[u][sum[u]],cut[u]);//一刀砍断,只需要一条边的花费,看是否能更新相应的状态,这里把边转换为了点权 } int get(char s){ if(s=='R') return 0; if(s=='G') return 1; if(s=='B') return 2; if(s=='I') return 3; if(s=='V') return 4; if(s=='Y') return 5; if(s=='O') return 6; } int main() { int t,i,j,k,n,tot,fa; char s[10]; scanf("%d",&t); while(t--) { for(i=0;i<=n;i++) edge[i].clear(); scanf("%d%d",&n,&tot); for(i=1;i<=n;i++) { scanf("%s%d%d%d",s,&qual[i],&fa,&cut[i]); col[i]=get(s[0]); edge[fa].push_back(i); edge[i].push_back(fa); }cut[0]=inf; int l=1,r=1000,mid; int best=-1; while(l<=r) { mid=(l+r)>>1; dfs(0,-1,mid); if(dp[0][(1<<7)-1]<=tot) { best=mid; l=mid+1; } else r=mid-1; } if(best==-1) printf("Stay hungry\n"); else printf("%d\n",best); } return 0; }
zoj 3527 带环的树形DP
/* 很好的树形DP,破环为树; 这道题目很特殊,看到的时候根本无从下手,因为以前没接触过这种类型的树形DP,网上搜了一下发现自己简直弱爆了。。 这是一类最简单的树形DP,考虑某个节点选或不选,然后把两种状态从叶子向根更新上去即可, 但是这道题中给你的不是一棵树,是图,特殊的图 每个点都只有一个父亲(或一个儿子),所以形成的环是简单环,所以可以考虑破环, 从环上的一个点出发开始DP,这个点的两种状态(选或不选)分别枚举一下 */ #include#include #include using namespace std; typedef long long lld; const lld inf = (lld)1<<50; const int M = 100010; int w[M]; int n; int head[M],nxt[M],pnt[M],wi[M],p[M]; int E; lld dp[M][2]; void add(int a,int b,int w) { pnt[E]=b; wi[E]=w; nxt[E]=head[a]; head[a]=E++; } void dfs(int u,int F) { dp[u][0]=0; dp[u][1]=w[u]; for(int i=head[u];i!=-1;i=nxt[i]) { if(pnt[i]!=F) dfs(pnt[i],F); dp[u][0]+=max(dp[pnt[i]][0],dp[pnt[i]][1]); dp[u][1]+=max(dp[pnt[i]][0],dp[pnt[i]][1]+wi[i]); } } lld solve(int u) { dp[u][0]=0; dp[u][1]=-inf;//不选u,这个状态不合法 lld ans1=0,ans2=0; for(int i=head[u];i!=-1;i=nxt[i]) { dfs(pnt[i],u); ans1+=max(dp[pnt[i]][0],dp[pnt[i]][1]); } dp[u][0]=-inf;//选u,这个状态不合法 dp[u][1]=w[u]; for(int i=head[u];i!=-1;i=nxt[i]) { dfs(pnt[i],u); ans2+=max(dp[pnt[i]][0],dp[pnt[i]][1]+wi[i]); } return max(ans1,ans2); } int vis[M]; int main() { while(scanf("%d",&n)!=EOF) { E=0; fill(head,head+n+1,-1); fill(p,p+n+1,-1); for(int i=1,j;i<=n;i++) { scanf("%d%d%d",&w[i],&j,&p[i]); add(p[i],i,j); } fill(vis,vis+n+1,-1); lld ans=0; for(int i=1,j;i<=n;i++) { if(vis[i]!=-1) continue; for(j=i;vis[j]==-1;j=p[j]) vis[j]=i; if(vis[j]==i) ans+=solve(j); } printf("%lld\n",ans); } return 0; }