树形dp

 #pragma comment(linker, "/STACK:1024000000,1024000000")

 https://www.cnblogs.com/kuangbin/archive/2012/10/05/2712222.html 

解决爆栈方式的一种做法。

Balancing Act

 POJ - 1655 

求树的重心。

树的重心就是这个节点最大的枝条最小。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N = 2e4 + 5;
vector<int> g[N];
int ma[N], num[N];
int n;

//根据树的重心的定义,我们发现判断一个点是不是重心 只要把这个点去掉
//看它剩下的子树结点的个数的最大值是不是最小就ok了
//子树有两种:一个是往下的即ma[i],另一个是往上的 即n - num[i]

void dfs(int u, int f) { num[u] = 1; ma[u] = 0; int len = g[u].size(); for(int i = 0; i < len; i++) { int v = g[u][i]; if(v == f) continue; dfs(v, u); ma[u] = max(ma[u], num[v]); num[u] += num[v]; } ma[u] = max(ma[u], n - num[u]); } int main() { int t; scanf("%d", &t); while(t--) { scanf("%d", &n); int u, v; for(int i = 1; i <= n; i++) g[i].clear(); for(int i = 1; i < n; i++) { scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); } dfs(1, 0); int mi = N, ind = 0; for(int i = 1; i <= n; i++) { if(ma[i] < mi) { mi = ma[i]; ind = i; } } printf("%d %d ", ind, mi); } return 0; }

求树的直径

https://blog.csdn.net/niiick/article/details/80708655

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=100010;
int n,m;
struct node{int v,dis,nxt;}E[maxn<<2];
int head[maxn],tot;
int dp[maxn],mxlen;

void add(int u,int v,int dis)
{
    E[++tot].nxt=head[u];
    E[tot].v=v; E[tot].dis=dis;
    head[u]=tot;
}

void DP(int u,int pa)
{
    dp[u]=0;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==pa) continue;
        DP(v,u);
        mxlen=max(mxlen,dp[u]+dp[v]+E[i].dis);
        dp[u]=max(dp[u],dp[v]+E[i].dis);
    } 
}

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(head,0,sizeof(head)); 
        tot=mxlen=0;
        while(m--)  
        {  
            int u=read(),v=read(),dis=read();
            char ss[5]; scanf("%s",&ss);
            add(u,v,dis);  add(v,u,dis);  
        }  
        DP(1,0);
        printf("%d",mxlen);
    }
    return 0;
}

求树上权值最大的路径

这个是求所有链中最大的,所以只要一边dfs就行,如果是经过的每个点的最大值才需要向上走与向下走,如下一题。

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=1e5+5;
int n,m;
struct node{
    int to,cost;
    node(int to,int cost){
        this->to=to;
        this->cost=cost;
    }
};
vector<node>ve[maxn];
int f[maxn];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
bool vis[maxn];
int dp[maxn][2];//0是次远的,1是最远的。
int ans;
void dfs(int x,int fa){
    vis[x]=true;
    for(int i=0;i<ve[x].size();i++){
        int to=ve[x][i].to;
        int cost=ve[x][i].cost;
        
        if(to==fa) continue;
        dfs(to,x);
        if (dp[x][1] < dp[to][1]+cost){
            dp[x][0] = dp[x][1];
            dp[x][1] = dp[to][1]+cost;
        }
        else if (dp[x][0] < dp[to][1]+cost)
            dp[x][0] = dp[to][1]+cost;    
    }  
    ans = max(ans, dp[x][1]+dp[x][0]);    
}
 
int main(){
    int x,y,z;
     while(~scanf("%d%d", &n, &m)){
        for(int i=1;i<=n;i++)f[i]=i,ve[i].clear();
        int flag=0;
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&x,&y,&z);
            ve[x].push_back(node(y,z));
            ve[y].push_back(node(x,z));
            int x1=find(x);int x2=find(y);
            
            if(x1==x2)flag=1;
            else f[x1]=x2;
        }
        if (flag) {printf("YES
"); continue;}
        memset(vis,false,sizeof(vis));
        memset(dp,0,sizeof(dp));
        ans=0;
        for(int i=1;i<=n;i++)//跑树dp 
            if(!vis[i]) dfs(i,0); //因为不成环,是很多个各不联通的分量,所以跑一遍循环
        
        printf("%d
", ans);
    }
    return 0;
}

http://acm.hdu.edu.cn/showproblem.php?pid=2196

树上,求一个节点最小的枝条的权值和最大。

https://www.cnblogs.com/WABoss/p/5267488.html

题解

这个是求所有点的经过他的权值最大的路径。所以不仅要记录往下走的,还要记录往上走的。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;
const int N = 1e4 + 5;

struct Node {
    int v, w;
    Node(int _v, int _w) : v(_v), w(_w) {}
};
vector<Node> g[N];
int dp[N][3], cnt[N];

void dfsdown(int u, int f) {
    int len = g[u].size();
    int ma0 = 0, ma1 = 0;
    cnt[u] = 1;
    for(int i = 0; i < len; i++) {
        int v = g[u][i].v;
        int w = g[u][i].w;
        if(v == f) continue;
        dfsdown(v, u);
        if(ma0 < dp[v][0] + w) {
            ma1 = ma0;
            ma0 = dp[v][0] + w;
            cnt[u] = 1;
        }
        else if(ma0 == dp[v][0] + w) {
            cnt[u]++;
        }
        else if(ma1 < dp[v][0] + w) {
            ma1 = dp[v][0] + w;
        }
    }
    dp[u][0] = ma0, dp[u][1] = ma1;
}

void dfsup(int u, int f) {
    int len = g[u].size();
    for(int i = 0; i < len; i++) {
        int v = g[u][i].v;
        int w = g[u][i].w;
        if(v == f) continue;
        if(cnt[u] > 1 || dp[v][0] + w < dp[u][0]) {
            dp[v][2] = max(dp[u][2] + w, dp[u][0] + w);
        }
        else dp[v][2] = max(dp[u][2] + w, dp[u][1] + w);
        dfsup(v, u);
    }
}

int main() {
    int n;
    while(~scanf("%d", &n)) {
        int v, w;
        for(int i = 1; i <= n; i++) {
            g[i].clear();
            dp[i][0] = dp[i][1] = dp[i][2] = 0;
        }
        for(int i = 2; i <= n; i++) {
            scanf("%d%d", &v, &w);
            g[i].push_back(Node(v, w));
            g[v].push_back(Node(i, w));
        }
        dfsdown(1, 0);
        dfsup(1, 0);
//        for(int i = 1; i <= n; i++) printf("%d %d %d %d
", dp[i][0], dp[i][1], dp[i][2], cnt[i]);
        for(int i = 1; i <= n; i++) printf("%d
", max(dp[i][0], dp[i][2]));
    }
}

http://acm.hdu.edu.cn/showproblem.php?pid=3534

题意:n 之后 n-1条边,l,r,w;求出树上的最长路径以及最长路径的条数。

//思路参考 http://www.cnblogs.com/a-clown/p/6010109.html    hdu2196

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int N = 1e5 + 5;

struct Node {
    int v, w;
    Node(int _v, int _w) : v(_v), w(_w) {}
};

int dp[N], cnt[N];
int ind[N];
vector<Node> g[N];
int n, ma, cn;

void dfsdown(int u, int f) {
    int len  = g[u].size();
    dp[u] = 0, cnt[u] = 1;
    for(int i = 0; i < len; i++) {
        int v = g[u][i].v;
        int w = g[u][i].w;
        if(v == f) continue;
        dfsdown(v, u);
        int tmp = dp[v] + w;
//每个dp[u]相当于前一种情况。相当于一个点找两条最大的枝条。
//这个要画图看一下。
if(tmp + dp[u] > ma) { ma = dp[u] + tmp; cn = cnt[u] * cnt[v]; } else if(tmp + dp[u] == ma) { cn += cnt[u] * cnt[v]; } if(tmp > dp[u]) { cnt[u] = cnt[v]; dp[u] = tmp; } else if(tmp == dp[u]) { cnt[u] += cnt[v]; } } } int main() { while(~scanf("%d", &n)) { int u, v, w; for(int i = 1; i <= n; i++) { g[i].clear(); ma = 0, cn = 0; } for(int i = 1; i < n; i++) { scanf("%d%d%d", &u, &v, &w); g[u].push_back(Node(v, w)); g[v].push_back(Node(u, w)); } dfsdown(1, 0); printf("%d %d ", ma, cn); } return 0; }

Terrorist’s destroy

 HDU - 4679 

 

树,然后每条边上有权值,可以删掉一条边,得到一个值val = 权值*(分出来的两棵树中最大的直径,算直径时不需要考虑权值),每条边得到一个最大的val值,然后在找到这个这个值最小的边,最后输出边的

编号。

首先两边dfs求出这个树的直径,然后删边的话,两种情况。

一种:不在直径上,那么val值就等于权值*直径长度。

  在直径上,那么求权值*两颗子树中最大的直径。

  (并不是很理解)

  那么需要求生成的两颗子树中的直径的最大值,树中有个结论就是去掉一条边后生成的两颗子树最大直径肯定是原来的树中一个端点出发的路径,那么就可以考虑用对原来树的直径的两个端点两次dfs用树形dp来解决问题了。

去掉一条边u-v后的最大直径,先算u->v,则去掉u->v后含v的子树最大直径有两种可能:

(1).从v出发的最大边+从v出发的次大边。

(2).v的子树的最大直径。

去掉v->u同理。

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>

using namespace std;

const int N = 1e5 + 5;
struct Node {
    int v, w, id;
    Node (int _v, int _w, int _id) : v(_v), w(_w), id(_id) {}
};

vector<Node> g[N];
int dp[N], fa[N], is[N];
int m0[N], m1[N], ma[N], resm, res, ori;
int val[N];

void dfs(int u, int f) {
    fa[u] = f;
    dp[u] = dp[f] + 1;
    int len = g[u].size();
    for(int i = 0; i < len; i++) {
        int v = g[u][i].v;
        if(v == f) continue;
        dfs(v, u);
    }
}

void dfss(int u, int f) {
    int len = g[u].size();
    m1[u] = m0[u] = ma[u] = 0;
    for(int i = 0; i < len; i++) {
        int v = g[u][i].v;
        if(v == f) continue;
        dfss(v, u);
        if(m0[v] + 1 > m1[u]) {
            m1[u] = m0[v] + 1;
            if(m1[u] > m0[u]) swap(m1[u], m0[u]);
        }
        ma[u] = max(ma[u], ma[v]);
    }
    ma[u] = max(ma[u], m1[u] + m0[u]);
}

void solve(int u, int f) {
    int len = g[u].size();
    for(int i = 0; i < len; i++) {
        int v = g[u][i].v;
        int w = g[u][i].w;
        int id = g[u][i].id;
        if(v == f) continue;
        if(is[u] && is[v]) {
            val[id] = max(val[id], w * ma[v]);
        }
        else {
            val[id] = max(val[id], ori * w);
        }
        solve(v, u);
    }
}

int main() {
    int t;
    scanf("%d", &t);
    int ca = 0;
    while(t--) {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            g[i].clear();
            is[i] = 0;
            val[i] = 0;
        }
        int u, v, w;
        for(int i = 1; i < n; i++) {
            scanf("%d%d%d", &u, &v, &w);
            g[u].push_back(Node(v, w, i));
            g[v].push_back(Node(u, w, i));
        }
        dp[0] = 0;
        dfs(1, 0);
        int s = 0, t = 0;
        int ma = 0;
        for(int i = 1; i <= n; i++) {
            if(dp[i] > ma) {
                ma = dp[i];
                s = i;
            }
        }
        dfs(s, 0);
        ma = 0;
        for(int i = 1; i <= n; i++) {
            if(dp[i] > ma) {
                ma = dp[i];
                t = i;
            }
        }
        ori = ma - 1;
        int ind = t;
        while(ind != s) {
            is[ind] = 1;
//            printf("cac
");
            ind = fa[ind];
        }
        resm = 0x3f3f3f3f;
        res = 0;
        dfss(s, 0);
        solve(s, 0);
        dfss(t, 0);
        solve(t, 0);
        for(int i = 1; i < n; i++) {
            if(val[i] < resm) {
                resm = val[i];
                res = i;
            }
        }
        printf("Case #%d: %d
", ++ca, res);
    }
}

题目描述
有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)
这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树

现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。

输入格式:
第1行2个数,N和Q(1<=Q<= N,1<N<=100)。
N表示树的结点数,Q表示要保留的树枝数量。接下来N-1行描述树枝的信息。
每行3个整数,前两个是它连接的结点的编号。第3个数是这根树枝上苹果的数量。
每根树枝上的苹果不超过30000个。

输出格式:
一个数,最多能留住的苹果的数量。

输入样例
5 2
1 3 1
1 4 10
2 3 20
3 5 20

输出样例
21

首先,题目中说了,这是颗二叉树
有什么用呢?
没什么用
其实这会让题目变得没有那么复杂,一个节点只有左右两个儿子,存图和遍历的时候就多了一种方式
这是树形背包的经典题

题意很简单,每条边有一个权值,保留若干条边,求去掉边后根节点能够到达的所有边的权值和最大是多少。

看到这道题,也许大家会想到数字三角形这道题,但是这里的这棵树并不是一颗满二叉树,甚至也不是一颗完全二叉树,所以我们不能用数字三角形的处理方式来做这道题。那该怎么思考呢?很明显,这是一个树状结构,我们可以从这点出发来考虑。这道题明确给出了根的位置,也就确定了父子节点的关系,我们会发现,对于每个父节点的状态,都是由它的子节点转移过来的,所以我们大概可以得出这里有一个由子节点转移到父节点的状态转移方程,又因为父节点子树上选择的边数完全取决于子节点的子树选择的边数。

f[u][i]=max(f[u][i],f[u][ij1]+f[v][j]+w)

f[u][i]表示以u为根节点的子树选择i条边权值和最大为多少,这实际上就是一个背包。为什么两个f数组的边数为i1条呢?因为我们若想取一颗子节点的子树上的边,那就必须取父节点与子节点相连的那条边。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc getchar
#define maxn 105
using namespace std;

inline ll read(){
    ll a=0;int f=0;char p=gc();
    while(!isdigit(p)){f|=p=='-';p=gc();}
    while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
    return f?-a:a;
}

struct ahaha{
    int w,to,next;
}e[maxn<<1];int tot,head[maxn];
inline void add(int u,int v,int w){
    e[tot].w=w,e[tot].to=v,e[tot].next=head[u];head[u]=tot++;
}int n,m;

int sz[maxn],f[maxn][maxn];
void dfs(int u,int fa){
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;if(v==fa)continue;
        dfs(v,u);sz[u]+=sz[v]+1;  //子树边数在加上子节点子树的基础上还要加一,也就是连接子节点子树的那条边
        for(int j=min(sz[u],m);j;--j)   //由于是01背包,所以要倒序DP
            for(int k=min(j-1,sz[v]);k>=0;--k)    //这一维正序倒序无所谓,但是把取min放在初始化里可以减少运算次数,算是一个优化的小习惯
                f[u][j]=max(f[u][j],f[u][j-k-1]+f[v][k]+e[i].w);
    }
}

int main(){memset(head,-1,sizeof head);
    n=read();m=read();
    for(int i=1;i<n;++i){    //前向星存边,要存两边,便于读取
        int u=read(),v=read(),w=read();
        add(u,v,w);add(v,u,w);
    }
    dfs(1,-1);
    printf("%d",f[1][m]);
    return 0;
}
原文地址:https://www.cnblogs.com/downrainsun/p/11134789.html