树形DP 学习总结

DP毕竟是算法中最精妙的部分,理解并玩得花哨还是需要一定的时间积累

之前对普通的DP也不敢说掌握,只能说略懂皮毛

在学习树形DP 的同时也算是对DP有了更深的理解吧

DP的关键就在于状态的定义以及找转移

首先要考虑清楚状态,状态要能够很好地并且完整地描述子问题

其次考虑最底层的状态,这些状态一般是最简单的情况或者是边界情况

再就是考虑某一个状态能从哪些子状态转移过来,同时还要考虑转移的顺序,确保子问题已经解决

树形DP很多时候就是通过子节点推父亲节点的状态

还是通过题目来加强理解吧

1.HDU 1520

题意:给一棵树,选最多的结点使得选择的结点不存在直接的父子关系

很容易想到一个结点有两个状态:选或者不选

所以自然地想到状态dp[u][0/1]表示u子树内的最佳答案,u的状态为选或者不选

初始化自然是叶子结点dp[u][0]=0,dp[u][1]=w[u]

转移则可以考虑依次考虑

u不选的时候:u的儿子可以任意选或者不选,所以dp[u][0]+=max(dp[v][0],dp[v][1])

u选的时候:u的儿子必定不能选,所以dp[u][1]+=dp[v][0]   然后dp[u][1]+=w[u]表示加上u这个点

答案自然就是max(dp[rt][0],dp[rt][1])了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 6005;
const int M = 1e5+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N],w[N],deg[N];
int dp[N][2];

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(deg,0);
    mems(dp,0);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
}

void dfs(int u){
    dp[u][0]=0;
    dp[u][1]=w[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        dfs(v);
        dp[u][0]+=max(dp[v][1],dp[v][0]);
        dp[u][1]+=dp[v][0];
    }
}
int n;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        int u,v;
        while(1){
            scanf("%d%d",&v,&u);
            if(!v&&!u) break;
            addedge(u,v);deg[v]++;
        }
        int rt;
        for(int i=1;i<=n;i++) if(!deg[i]){
            dfs(rt=i);
            break;
        }
        printf("%d\n",max(dp[rt][0],dp[rt][1]));
    }
    return 0;
}

2.POJ 1436

题意:选中一个点则与其相连的边将被覆盖,问最少选几个点使得树中所有边均被覆盖

和上一个题很类似

同样状态设为dp[u][0/1]

初始的底层状态自然是dp[u][0]=0,dp[u][1]=1;

考虑一个非叶子结点和它儿子的所有连边

如果当前结点不选,那这些边只能由其儿子保护,所以dp[u][0]+=dp[v][1]

如果当前结点选,那这些边已被保护,其儿子选和不选都行,所以dp[u][1]+=min(dp[v][0],dp[v][1])

答案自然是min(dp[rt][0],dp[rt][1])

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1505;
const int M = 1e5+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N],deg[N];
int dp[N][2];

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(deg,0);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
}

void dfs(int u){
    dp[u][0]=0;
    dp[u][1]=1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        dfs(v);
        dp[u][0]+=dp[v][1];
        dp[u][1]+=min(dp[v][0],dp[v][1]);
    }
}

int n,k,u,v;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        for(int i=1;i<=n;i++){
            scanf("%d:(%d)",&u,&k);
            for(int j=0;j<k;j++){
                scanf("%d",&v);
                addedge(u,v);deg[v]++;
            }
        }
        int rt;
        for(int i=0;i<n;i++) if(!deg[i]){
            dfs(rt=i);
            break;
        }
        printf("%d\n",min(dp[rt][0],dp[rt][1]));
    }
    return 0;
}

3.URAL 1018

题意:树中每个点有权值,问只留下k个点剩下的最大权值和是多少?留下的点还是构成一棵树

树形背包

状态定义成dp[u][i]表示u子树内剩i个点的最大权值

考虑叶子结点:dp[u][0]=0,dp[u][1]=w[u]

考虑非叶子结点的一个状态dp[u][i],对于当前的一个儿子v,枚举一个k表示从这个儿子里取几个结点,维护一个最大值

其实我们这里的状态是三维的,表示u子树的前j个子树取了i个结点的答案

我们使用滚动数组把j这一维滚掉

这里简化了题目,每一个结点固定只有两个儿子,用一般做法做肯定也是没问题的

还有要注意的地方就是这里根是一定要保留的

处理方法就是对于状态dp[u][1]直接赋值,枚举时候i从2开始,这样就可以默认根已取

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 105;
const int M = 1e5+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N],w[N];
int dp[N][N],sz[N];
int ls[N],rs[N];

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(w,-1);
    //mems(deg,0);
    mems(dp,0);
    mems(ls,-1);
    mems(rs,-1);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    sz[u]=1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        if(ls[u]==-1) ls[u]=v;
        else rs[u]=v;
    }
}

void dfs(int u){
    int f=0;
    dp[u][0]=0;dp[u][1]=w[u];
    if(ls[u]!=-1) dfs(ls[u]),f=1;
    if(rs[u]!=-1) dfs(rs[u]),f=1;
    if(!f) return;
    for(int i=2;i<=sz[u];i++)
    for(int j=0;j<=sz[ls[u]];j++) if(i-1>=j) dp[u][i]=max(dp[u][i],dp[ls[u]][j]+dp[rs[u]][i-1-j]+w[u]);
}

int n,k;

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&k)){
        init();
        int u,v,x,rt=1;w[rt]=0;
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&x);
            addedge(u,v);
            if(w[v]==-1) w[v]=x;
            else w[u]=x;
        }
        dfs1(rt,-1);
        dfs(rt);
        printf("%d\n",dp[rt][k+1]);
    }
    return 0;
}

4.HDU 2196

题意:对于树中的每一个结点,输出树中与其距离最远的结点的距离值

经典的树形DP问题

状态定义为dp[u][0/1]为u到其子树内结点的最远距离、次远距离

这样一轮dp下来,可以想到对于根来说,dp[rt][0]就是它的答案

但是对于其它结点来说只得到了其子树内的答案,而我们需要的是其对于整棵树的信息

这里需要再一次dfs,相当于反过来从根往叶子再dp一次,通过根的答案推其余结点的答案

这里之所以要维护一个次大值,就是对于一个结点u的儿子v,

若u的最远距离是经过u的,那v的答案应该是v子树内的答案和u的次大值比较,否则v的答案和u的最大值比较

画个图就明白了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e4+5;
const int M = 2e4+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N];
int mx[N][2],id[N][2];

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(mx,0);
    mems(id,-1);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        if(mx[v][0]+edge[i].w>=mx[u][0]){
            mx[u][1]=mx[u][0];
            id[u][1]=id[u][0];id[u][0]=v;
            mx[u][0]=mx[v][0]+edge[i].w;
        }
        else if(mx[v][0]+edge[i].w>mx[u][1]) mx[u][1]=mx[v][0]+edge[i].w,id[u][1]=v;
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        if(id[u][0]==v){
            if(mx[v][1]<mx[u][1]+edge[i].w){
                mx[v][1]=mx[u][1]+edge[i].w;
                id[v][1]=u;
            }
        }
        else{
            if(mx[v][1]<mx[u][0]+edge[i].w){
                mx[v][1]=edge[i].w+mx[u][0];
                id[v][1]=u;
            }
        }
        if(mx[v][0]<mx[v][1]){
            swap(mx[v][0],mx[v][1]);
            swap(id[v][0],id[v][1]);
        }
        dfs2(v,u);
    }
}

int n,u,w;

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        for(int i=2;i<=n;i++){
            scanf("%d%d",&u,&w);
            addedge(i,u,w);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        for(int i=1;i<=n;i++) printf("%d\n",mx[i][0]);
    }
    return 0;
}

5.POJ 2152

题意:树中每个结点有两个值:w[i]表示在i建设防火设施的价格,d[i]表示与i最近的防火设施的距离上限,求满足所有d[i]的最小花费

算是一道比较难的树形dp,状态和普通的树形DP略有不同

树形dp很多时候是把一个结点及其子树看成一个整体,然后考虑这个结点的状态进行转移

考虑到数据范围N<=1000,可以定义状态dp[u][i]表示u依靠i时,保证子树内安全的最小花费

为了转移方便,再定义all[u]表示保证u的安全的最小花费

其实可以理解为all[u]是在dp[u][i]中取了个最优值

要确定一个点是否能被u依靠就需要知道u与该点的距离

所以先n^2处理树中任意两点的距离

考虑叶子结点:dp[u][i]=w[i]

考虑一个非叶子结点u,先枚举它依靠的结点i

再考虑u的儿子v,v可以选择依靠或者不依靠i,则dp[u][i]+=min(dp[v][i]-c[i],all[v])

对于u的每一个i更新u的最优解all[u]

对于u的孙子k是不需要考虑的,因为k依靠i的情况在解决v的时候已经考虑到了,所以不会有重复计算的情况

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#define ls pos<<1
#define rs pos<<1|1
#define lson L,mid,pos<<1
#define rson mid+1,R,pos<<1|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e3+5;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N];
int dp[N][N],all[N];
int n,u,v,x;
int cost[N],d[N],dis[N][N];

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(all,INF);
    mems(dp,INF);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

void dfs1(int rt,int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dis[rt][v]=dis[rt][u]+edge[i].w;
        dfs1(rt,v,u);
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs2(v,u);
    }
    for(int k=1;k<=n;k++) if(dis[u][k]<=d[u]){
        dp[u][k]=cost[k];
        for(int i=first[u];i!=-1;i=edge[i].next){
            int v=edge[i].e;
            if(v==fa) continue;
            dp[u][k]+=min(dp[v][k]-cost[k],all[v]);
        }
        all[u]=min(all[u],dp[u][k]);
    }
}

int T;
int main(){
    //freopen("in.txt","r",stdin);
    for(int i=0;i<N;i++) dis[i][i]=0;
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&cost[i]);
        for(int i=1;i<=n;i++) scanf("%d",&d[i]);
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&x);
            addedge(u,v,x);
        }
        for(int i=1;i<=n;i++) dfs1(i,i,-1);
        dfs2(1,-1);
        printf("%d\n",all[1]);
    }
    return 0;
}

6.POJ 3162

题意:对于树中每一个结点i找到另一个结点使得两者距离dp[i]最远,最后输出一段最长区间的长度,区间maxv-minv<=M

只是在树形dp上加了点东西而已,用线段树+two pointer维护就好了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#define ls pos<<1
#define rs pos<<1|1
#define lson L,mid,pos<<1
#define rson mid+1,R,pos<<1|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e6+5;
const int M = 2e6+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N];
int dp[N][2],id[N][2];

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

void update(int u){
    if(dp[u][0]<dp[u][1]){
        swap(dp[u][0],dp[u][1]);
        swap(id[u][0],id[u][1]);
    }
}

void dfs1(int u,int fa){
    dp[u][1]=dp[u][0]=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        if(edge[i].w+dp[v][0]>dp[u][1]){
            dp[u][1]=edge[i].w+dp[v][0];
            id[u][1]=v;
        }
        update(u);
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        if(id[u][0]==v){
            if(dp[u][1]+edge[i].w>dp[v][1]){
                dp[v][1]=dp[u][1]+edge[i].w;
                id[v][1]=u;
            }
        }
        else{
            if(dp[u][0]+edge[i].w>dp[v][1]){
                dp[v][1]=dp[u][0]+edge[i].w;
                id[v][1]=u;
            }
        }
        update(v);
        dfs2(v,u);
    }
}

int minv[N<<2],maxv[N<<2];

void build(int L,int R,int pos){
    if(L==R){
        minv[pos]=maxv[pos]=dp[L][0];
        return;
    }
    mdzz;
    build(lson);
    build(rson);
    minv[pos]=min(minv[ls],minv[rs]);
    maxv[pos]=max(maxv[ls],maxv[rs]);
}

int query_min(int l,int r,int L,int R,int pos){
    if(l<=L&&R<=r) return minv[pos];
    mdzz;
    int ans=INF;
    if(l<=mid) ans=min(ans,query_min(l,r,lson));
    if(r>mid) ans=min(ans,query_min(l,r,rson));
    return ans;
}

int query_max(int l,int r,int L,int R,int pos){
    if(l<=L&&R<=r) return maxv[pos];
    mdzz;
    int ans=-INF;
    if(l<=mid) ans=max(ans,query_max(l,r,lson));
    if(r>mid) ans=max(ans,query_max(l,r,rson));
    return ans;
}

int n,m,u,v;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=2;i<=n;i++){
            scanf("%d%d",&u,&v);
            addedge(i,u,v);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        int ans=0;
        build(1,n,1);
        int l=1,r=1;
        while(1){
            if(l>r||l>n||r>n) break;
            int mxv=query_max(l,r,1,n,1);
            int miv=query_min(l,r,1,n,1);
            if(mxv-miv<=m){
                ans=max(ans,r-l+1);
                r++;
            }
            else l++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

7.codeforces 219D

题意:树边有方向,选择一个点,翻转最少路径,使得其能到达其余所有的点,输出所有可能的答案

可以将翻转理解为一种花费,那不翻转就是花费0,翻转则为1

可以定义dp[u]表示u到所有点的距离和,那dp[u]最小的就是答案

依旧先考虑u的子树内的答案

考虑一个叶子:dp[u]=0;

考虑一个非叶子u以及其的一个儿子v:很容易想到dp[u]+=dp[v]+w[u,v]

一次dfs下来后rt的答案已知,接下来通过rt来推其余结点对于整棵树的答案

考虑结点u及其一个儿子v,v到v子树内的答案已知,v到u除v子树的结点的答案是dp[u]-dp[v]-w[u,v]

所以dp[v]+=dp[u]-dp[v]-w[u,v]+w[v,u]

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 2e5+5;
const int M = 4e5+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N];
int dp[N],sz[N];

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u],0);
    first[u]=tot++;
    edge[tot]=node(u,first[v],1);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    dp[u]=0;sz[u]=1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        dp[u]+=dp[v]+edge[i].w;
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dp[v]+=(dp[u]-dp[v]-edge[i].w)+(edge[i].w^1);
        dfs2(v,u);
    }
}

int n,u,v;
int main(){
    init();
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
    }
    dfs1(1,-1);
    dfs2(1,-1);
    int ans=INF;
    for(int i=1;i<=n;i++) ans=min(ans,dp[i]);
    //for(int i=1;i<=n;i++) cout<<i<<‘ ‘<<dp[i]<<endl;
    printf("%d\n",ans);
    for(int i=1;i<=n;i++) if(ans==dp[i]) printf("%d ",i);
    return 0;
}

8.POJ 1155

题意:树中每个叶子有点权表示收入,边权表示花费。选择某些叶子后,不必要的边可删掉,最多选择几个点使得花费>=收入

(未完待续)

时间: 2024-10-09 04:56:55

树形DP 学习总结的相关文章

树形dp学习

学习博客:https://www.cnblogs.com/qq936584671/p/10274268.html 树的性质:n个点,n-1条边,任意两个点之间只存在一条路径,可以人为设置根节点,对于任意一个节点只存在至多一个父节点,其余为子节点. 记忆化树形dp模型较为抽象难以理解,以下通过由浅到深的方式解析树形dp以及树的性质. 树形dp求树的直径:(在一颗树里找到点X,Y,使得|XY|最大) 如图,我们令A为根节点,令dfs遍历顺序为ABDGHEFC. 在我们的dfs计算过程中,我们从下往上

树形dp学习笔记

直接放例题吧: codevs1380:没有上司的舞会 题意: 某公司要举办一次晚会,但是为了使得晚会的气氛更加活跃,每个参加晚会的人都不希望在晚会中见到他的直接上司,现在已知每个人的活跃指数和上司关系(当然不可能存在环),求邀请哪些人(多少人)来能使得晚会的总活跃指数最大. 思路: 任何一个点的取舍可以看作一种决策,那么状态就是在某个点取的时候或者不取的时候,以他为根的子树能有的最大活跃总值.分别可以用f[i,1]和f[i,0]表示第i个人来和不来. 当i来的时候,dp[i][1] += dp[

poj 1655 and 3107 and 2378 树形dp(树的重心问题)

简单的树形dp,顺便学习了树的重心的概念,即以该点为根的树的最大子树的结点数最少. poj 1655: 1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 6 const int N = 20001; 7 int head[N]; 8 int balance[N]; 9 int child[N]; 10 int n, e; 11 12 struct

bzoj2500: 幸福的道路(树形dp+单调队列)

好题.. 先找出每个节点的树上最长路 由树形DP完成 节点x,设其最长路的子节点为y 对于y的最长路,有向上和向下两种情况: down:y向子节点的最长路g[y][0] up:x的次长路的g[x][1]+dis[x][y] up:up[fa[x]]+dis[x][y] dfs1找向下,即向子节点的最长路 dfs2找向上的最长路 最后最长路f[i]=max(up[x],g[x][0]) 第二部分 找最长连续子序列,使得序列中abs(mx-mn)<=m 这次学习了用单调队列的做法 两个队列mx,mn

青云的机房组网方案(简单+普通+困难)(虚树+树形DP+容斥)

题目链接 1.对于简单的版本n<=500, ai<=50 直接暴力枚举两个点x,y,dfs求x与y的距离. 2.对于普通难度n<=10000,ai<=500 普通难度解法挺多 第一种,树形dp+LCA 比赛的时候,我猜测对于不为1的n个数,其中两两互质的对数不会很多,肯定达不到n^2 然后找出所有互质的对数,然后对为1的数进行特殊处理.(初略的估计了下,小于500的大概有50个质数,将n个数平均分到这些数中,最后大概有10000*50*200=10^7) 对所有的非1质数对,采用离

HDU 2242 连通分量缩点+树形dp

题目大意是: 所有点在一个连通图上,希望去掉一条边得到两个连通图,且两个图上所有点的权值的差最小,如果没有割边,则输出impossible 这道题需要先利用tarjan算法将在同一连通分量中的点缩成一个点后,重新构建一幅图,然后利用新建的图进行树形dp解决问题 这道题目需要注意的是可能存在重边,那么子节点回到父节点有两条边及以上的话,就需要对子节点经过父节点的边进行low值更新 tarjan算法学习的网站个人感觉还不错https://www.byvoid.com/blog/scc-tarjan/

【BZOJ-1060】时态同步 树形DP (DFS爆搜)

1060: [ZJOI2007]时态同步 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2101  Solved: 595[Submit][Status][Discuss] Description 小Q在电子工艺实习课上学习焊接电路板.一块电路板由若干个元件组成,我们不妨称之为节点,并将其用数 字1,2,3….进行标号.电路板的各个节点由若干不相交的导线相连接,且对于电路板的任何两个节点,都存在且仅 存在一条通路(通路指连接两个元件的导线序列).

洛谷P2014 选课 (树形dp)

10月1日更新.题目:在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程a是课程b的先修课即只有学完了课程a,才能学习课程b).一个学生要从这些课程里选择M门课程学习,问他能获得的最大学分是多少?输入第一行有两个整数N,M用空格隔开.(1<=N<=200,1<=M<=150)接下来的N行,第I+1行包含两个整数ki和s

树形dp入门-加分二叉树(luogu1040)

今天学习了树形dp,确实,感受到了深深的压力...一会还得去写选课那道题... 先看题目: 首先我们看到关键字:中序遍历.既然已经给出我们分数的算法,所以我们就可以通过枚举根节点来解决问题.在每一个根节点下求最优解,且记录下每一个根节点,就恰好能完成两个任务.即我们需要写两个子程序. 看代码:(请勿直接复制粘贴) #include <stdio.h> int n,point[32],f[32][32],mumber[32][32]; long long tree(int left,int ri