树形DP

加分二叉树

试题描述

    设一个 n 个节点的二叉树 tree 的中序遍历为 (1,2,3,⋯,n),其中数字 1,2,3,⋯ 为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di​​ ,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:记 subtree 的左子树加分为 l,右子树加分为 r,subtree 的根的分数为 a,则 subtree 的加分为:l×r+a 若某个子树为空,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。试求一棵符合中序遍历为 (1,2,3,⋯,n) 且加分最高的二叉树 tree。

    要求输出:tree 的最高加分和 tree 的前序遍历。

输入
第一行一个整数 n 表示节点个数;第二行 n 个空格隔开的整数,表示各节点的分数。
输出
第一行一个整数,为最高加分 b;第二行包含 n 个整数,两两之间用一个空格分隔,为该树的前序遍历。
输入示例
5
5 7 1 2 10
输出示例
145
3 1 2 4 5
其他说明
数据范围:对于 100% 的数据,n<30,b<100,结果不超过 4×10^​9​​ 。

 

先用树形DP做,但是所有根节点都需要枚举,时间复杂度和暴力差不多
其实这道题应该是区间DP,回顾经典区间DP石子合并,合并过程其实就是一颗树(两个石子是子节点,合成根节点)。与这道题类似
令f[i][j]表示从i到j区间合并(加分)的最大值,令k为区间根节点,易得出:f[i][j]=max(f[i][j],f[i][k-1]*f[k+1][j]+a[k]);
初始化:f[i][i+1]=1;(空节点是1),f[i][i]=a[i]
答案:f[1][n]
然后就是求二叉树的先序遍历,其实就是求合并路径,我们将g[i][j]表示从i到j的根节点,如果f[i][k-1]*f[k+1][j]+a[k]大于f[i][j]就将g[i][j]替换为k
得到g以后根据g进行搜索,最后得出答案。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <stack>
#include <vector>
using namespace std;
#define MAXN 110
#define INF 10000009
#define MOD 10000007
#define LL long long    
#define in(a) a=read()
#define REP(i,k,n) for(int i=k;i<=n;i++)
#define DREP(i,k,n) for(int i=k;i>=n;i--)
#define cl(a) memset(a,0,sizeof(a))
inline int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
inline void out(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) out(x/10);
    putchar(x%10+'0');
}
int input[MAXN];
int n;
int dp[MAXN][MAXN][2]; 
int ds=0;
inline void DFS(int left,int right){
    if(left>right)  return ;
    out(dp[left][right][1]);
    ds++;
    if(ds!=n)//为了保证不输出最后一个空格 
        printf(" ");
    DFS(left,dp[left][right][1]-1);//注意一定要先左再右 
    DFS(dp[left][right][1]+1,right);
    return ;
} 
int main(){
    in(n);
    REP(i,1,n){
        in(input[i]);
        dp[i][i][0]=input[i];
        dp[i][i][1]=i; 
        dp[i][i-1][0]=1;//所有空子树加分为1,0会炸,比如345这颗树5和34合并时会算作0 
    }
    REP(L,2,n)
        REP(i,1,n-L+1){
            int j=i+L-1;
            REP(k,i,j)
                if(dp[i][k-1][0]*dp[k+1][j][0]+input[k]>dp[i][j][0]){//DP转移方程,很好理解 
                    dp[i][j][1]=k;
                    dp[i][j][0]=dp[i][k-1][0]*dp[k+1][j][0]+input[k];
                }
        }
    cout<<dp[1][n][0]<<endl;
    DFS(1,n);//输出路径 
    return 0;    
} 

旅游规划

试题描述

    W 市的交通规划出现了重大问题,市政府下定决心在全市各大交通路口安排疏导员来疏导密集的车流。但由于人员不足,W 市市长决定只在最需要安排人员的路口安排人员。具体来说,W 市的交通网络十分简单,由 n 个交叉路口和 n−1 条街道构成,交叉路口路口编号依次为 0,1,⋯,n−1 。任意一条街道连接两个交叉路口,且任意两个交叉路口间都存在一条路径互相连接。

    经过长期调查,结果显示,如果一个交叉路口位于 W 市交通网最长路径上,那么这个路口必定拥挤不堪。所谓最长路径,定义为某条路径 p=(v1,v2,v3,……,vk),路径经过的路口各不相同,且城市中不存在长度大于 k 的路径,因此最长路径可能不唯一。因此 W 市市长想知道哪些路口位于城市交通网的最长路径上。

 

输入
第一行一个整数 n;之后 n−1 行每行两个整数 u,v,表示 u 和 v 的路口间存在着一条街道。
输出
包括若干行,每行包括一个整数——某个位于最长路径上的路口编号。为了确保解唯一,请将所有最长路径上的路口编号按编号顺序由小到大依次输出。
输入示例
10
0 1
0 2
0 4
0 6
0 7
1 3
2 5
4 8
6 9
输出示例
0
1
2
3
4
5
6
8
9
其他说明
数据范围:对于全部数据 1 <= n <= 2×100 000​​ 。

这道题是求一个树上的最长链,很明显应该用树形DP
其中f1[i]代表以i为根节点,它的所有子树的最大深度,f2则是最长深度
令j等于i的其中一个子节点,得出f[i]=max(f[i],f[j]+1);如果要记录次长,则可变为:
如果f1[j]+1大于f1[i],则f2[i]=f1[i];f1[i]=f[j]+1;
else 如果f1[j]+1大于f2[i],则f2[i]=f1[j]+1;
其中答案是max(f1[i]+f2[i]);

然后就是求路径,我们记录g[i]表示i节点除子树以外的最长路径
令f为i节点的父亲,则得出:
g[i]=max(g[f]+1,f1[f]+1);
但是问题来了,f1[f]当时就是从i更新来的(也就是i在f的最深子树上)怎么办?
我们需记录c[f]表示f接点的最深子树的直系节点,也就是f1[f]是从哪里传过来的。
判断,如果i!=c[f],则g[i]=max(g[f]+1,f1[f]+1);
else g[i]=max(g[f]+1,f2[f]+1);
最后,判断如果一个节点g[i]+f1[i]==ans,就将其输出。

 

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <stack>
#include <vector>
using namespace std;
#define MAXN 200010
#define INF 10000009
#define MOD 10000007
#define LL long long
#define in(a) a=read()
#define REP(i,k,n) for(int i=k;i<=n;i++)
#define DREP(i,k,n) for(int i=k;i>=n;i--)
#define cl(a) memset(a,0,sizeof(a))
inline int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
inline void out(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) out(x/10);
    putchar(x%10+'0');
}
int head[MAXN],to[2*MAXN],nxt[2*MAXN],total;
inline void adl(int a,int b){
    total++;
    to[total]=b;
    nxt[total]=head[a];
    head[a]=total;
    return ;
}
int f1[MAXN],f2[MAXN],c1[MAXN],c2[MAXN],ans,g[MAXN];
int root,n; 
inline int DFS1(int u,int f){//计算ans 
    for(int e=head[u];e;e=nxt[e]){
        if(to[e]!=f){
            int d=DFS1(to[e],u)+1;
            if(d>f1[u]){
                f2[u]=f1[u];
                c2[u]=c1[u];
                f1[u]=d;
                c1[u]=to[e];
            }
            else if(d>f2[u]){
                f2[u]=d;
                c2[u]=to[e];
            }
        }
    }
    if(f1[u]+f2[u]>ans){//更新ans 
        ans=f1[u]+f2[u];
        root=u;
    }
    return f1[u];
}
inline void DFS2(int u,int f){//寻找点 
    if(u!=0){
        if(u!=c1[f])  g[u]=max(g[f]+1,f1[f]+1);
        else  g[u]=max(g[f]+1,f2[f]+1);
        }
    for(int e=head[u];e;e=nxt[e])
        if(to[e]!=f)
            DFS2(to[e],u);
    return ;
}
int main(){
    in(n);
    REP(i,1,n-1){
        int a,b;
        in(a);in(b);
        adl(a,b);
        adl(b,a);
    }
    DFS1(0,0);
    DFS2(0,0);
    REP(i,0,n-1)  if(g[i]+f1[i]==ans || f1[i]+f2[i]==ans)  cout<<i<<endl;//输出答案 
    return 0;
}

周年纪念晚会

 

试题描述

Ural 州立大学的校长正在筹备学校的 80 周年纪念聚会。由于学校的职员有不同的职务级别,可以构成一棵以校长为根的人事关系树。每个资源都有一个唯一的整数编号,从 1 到 N 编号,且对应一个参加聚会所获得的欢乐度。为使每个职员都感到快乐,校长设法使每个职员和其直接上司不会同时参加聚会。你的任务是设计一份参加聚会者的名单,使总欢乐度最高。

输入
第一行是一个整数 N;接下来 N 行对应 N 个职员的欢乐度,第 i 行的一个整数为第 i 个职员的欢乐度 pi​ ;接着是学校的人事关系树,每一行格式为 L K ,表示第 K 个职员是第 L 个职员的直接上司,输入以 0 0 结束。
输出
输出参加聚会者获得的最大欢乐度。
输入示例
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
输出示例
5
其他说明
数据范围:对于 100% 的数据,1 <= N <= 6000,−128 <= pi <= 127 。

这题比较简单,因为领导之间的关系是树形结构,自然要用树形DP。
记录f[i][1]表示以i为根节点的子树,如果i去的最大值
f[i][0]表示以i为根子树,i不去的最大值
令j为i的儿子之一
易得转移方程:f[i][1]+=f[j][0];f[i][0]+=max(f[i][1],f[i][0]);
初始化:f[i][1]=a[i];
答案:max(f[root][1],max[root][0]);

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <stack>
#include <vector>
using namespace std;
#define MAXN 6010
#define INF 10000009
#define MOD 10000007
#define LL long long
#define in(a) a=read()
#define REP(i,k,n) for(int i=k;i<=n;i++)
#define DREP(i,k,n) for(int i=k;i>=n;i--)
#define cl(a) memset(a,0,sizeof(a))
inline int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
inline void out(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) out(x/10);
    putchar(x%10+'0');
}

int input[MAXN],total,head[MAXN],nxt[MAXN*2],to[MAXN*2],n,f[MAXN][2];
inline void adl(int a,int b){
    total++;
    to[total]=b;
    nxt[total]=head[a];
    head[a]=total;
    return ;
}
inline void init(){
    REP(i,1,n)  f[i][1]=input[i];
    return ;
}
inline void DFS(int u,int fa){//记忆化搜索 
    for(int e=head[u];e;e=nxt[e])
        if(to[e]!=fa){
            DFS(to[e],u);
            f[u][1]+=f[to[e]][0];
            f[u][0]+=max(f[to[e]][1],f[to[e]][0]);
        }
    return ;
}
int main(){
    in(n);
    REP(i,1,n)  in(input[i]);
    REP(i,1,n-1){
        int a,b;
        in(a);in(b);
        adl(a,b);
        adl(b,a);
    }
    int a,b;
    cin>>a>>b;
    init();
    DFS(1,0);
    cout<<max(f[1][0],f[1][1]);
    return 0;
}

叶子的颜色

 

试题描述

    给一棵有 m 个节点的无根树,你可以选择一个度数大于 1 的节点作为根,然后给一些节点(根、内部节点、叶子均可)着以黑色或白色。你的着色方案应保证根节点到各叶子节点的简单路径上都包含一个有色节点,哪怕是叶子本身。对于每个叶子节点 u,定义 cu 为从根节点到 u 的简单路径上最后一个有色节点的颜色。给出每个 cu​  的值,设计着色方案使得着色节点的个数尽量少。

输入
第一行包括两个数 m 和 n,依次表示节点总数和叶子个数,节点编号依次为 1 至 m。接下来 n 行每行一个 0 或 1 的数,其中 0 表示黑色,1 表示白色,依次为 c1,c2,⋯,cn。接下来 m−1 行每行两个整数 a,b,表示节点 a 与 b 有边相连。
输出
输出仅一个数,表示着色节点数的最小值。
输入示例
5 3
0
1
0
1 4
2 5
4 5
3 5
输出示例
2
其他说明
数据 1 2 3 4 5 6 7 8 9 10
M 10 50 100 200 400 1000 4000 8000 10000 10000
N 5 23 50 98 197 498 2044 4004 5021 4996


因为在树上跑,当然要用树形DP
其中f[i][1]表示i节点涂白色,它的子树满足条件需要的最少染色点
f[i][0]表示黑色,f[i][2]表示不涂
如果i涂黑色,那么他的儿子可以不涂,白色也同理,所以:
f[i][1]+=min(f[j][0],f[j][2],f[j][1]-1);
f[i][0]+=min(f[j][1],f[j][2],f[j][0]-1);
如果i什么都不涂,那么就没有什么可以节省的了
f[i][2]+=min(f[j][1],f[j][2],f[j][0]);
这题主要考初始化
如果i为叶子节点,它需要1,那么f[i][1]=1,并且不能涂白色或者不涂(因为要满足条件),所以f[i][0]=INF,f[i][2],0同理
对于非叶子节点节点,f[i][0]=f[i][1]=1(因为要涂的化至少有1)
最后答案是 max(f[root][1],f[root][0])(理论上根节点必须涂)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <stack>
#include <vector>
using namespace std;
#define MAXN 10010
#define INF 10000009
#define MOD 10000007
#define LL long long
#define in(a) a=read()
#define REP(i,k,n) for(int i=k;i<=n;i++)
#define DREP(i,k,n) for(int i=k;i>=n;i--)
#define cl(a) memset(a,0,sizeof(a))
inline int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
    return x*f;
}
inline void out(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) out(x/10);
    putchar(x%10+'0');
} 
int total,nxt[MAXN<<1],head[MAXN],to[MAXN<<1],f[MAXN][3],n,m;
inline void adl(int a,int b){
    total++;
    to[total]=b;
    nxt[total]=head[a];
    head[a]=total;
    return ;
}
inline void DFS(int u,int fa){
    if(u>m){
        f[u][1]=1;
        f[u][0]=1;
        f[u][2]=0;
    }
    for(int e=head[u];e;e=nxt[e])
        if(to[e]!=fa){
            DFS(to[e],u);
            f[u][1]+=min(f[to[e]][0],min(f[to[e]][2],f[to[e]][1]-1));
            f[u][0]+=min(f[to[e]][1],min(f[to[e]][2],f[to[e]][0]-1));
            f[u][2]+=min(f[to[e]][1],min(f[to[e]][2],f[to[e]][0]));
        }
    return ;
}
inline void init(){
    REP(i,1,m)  f[i][1]=f[i][0]=f[i][2]=INF;
    return ;
}
int main(){
    in(n);in(m);
    init();
    REP(i,1,m){
        int a;
        in(a);
        f[i][a]=1;
    }
    REP(i,1,n-1){
        int a,b;
        in(a);in(b);
        adl(a,b);
        adl(b,a);
    }
    DFS(m+1,0);
    cout<<min(f[m+1][1],f[m+1][0]);
    return 0;
}
/*
5 3
0
1
0
1 4
2 5
4 5
3 5
*/

 

posted @ 2018-08-20 13:43  Dijkstra·Liu  阅读(555)  评论(0编辑  收藏  举报