树上乱搞 - 树形dp

  树形dp,本质上就是遍历一颗树,维护某些值,使得原本很大的枚举量,降到O(n), 结合 dfs,进行状态转移。

  有时由上往下递推,有时由下往上递推

  往往满足最优子结构的特点,就是说局部最优,可以推出全局最优,因为每一步都深深受上一步影响。

  

Anniversary party

 HDU - 1520 

题意:一棵树,每个节点有权值,他和他的父结点只能选一个,求最大权值和,
设dp(i,0)表示这个点不选,dp(i,1)表示这个点选择。
那么对于他的子节点 dp[u][0]+=max(dp[v][0],dp[v][1]);
dp[u][1]+=dp[v][0];
初始化dp(i,0)为0. dp(i,1)为a[i],

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=6e3+5;
vector<int>e[N];
#define pb push_back
int dp[N][2],w[N],pre[N];
void dfs(int u){
    dp[u][0]=0;
    dp[u][1]=w[u];
    for(int i=0;i<e[u].size();i++){
        int v=e[u][i];
        dfs(v);
        dp[u][0]+=max(dp[v][0],dp[v][1]);
        dp[u][1]+=dp[v][0];
    }
}
int main(){
    int n;
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++)scanf("%d",&w[i]);
        int u,v;
        for(int i=1;i<=n;i++)e[i].clear(),pre[i]=-1;
            memset(dp,0,sizeof dp);
        while(~scanf("%d %d",&u,&v),u+v){
            e[v].pb(u);
            pre[u]=v;
        }
        int t=1;
        while(pre[t]!=-1)t=pre[t];
        dfs(t);
        printf("%d\n",max(dp[t][0],dp[t][1]));
    
    }
    // system("pause");
    return 0;
}
View Code

 

Computer

 HDU - 2196 

题意:一棵树,每条边有权值,求每个点距离最大点,1e5数据
考虑一个点最远的点会在哪里,只有两种情况:
1. 在子树上;
2. 在这颗子树外;
那么设 f(u) 表示这个点子树上最远的点距离,
有 f(u)=max(f(v)+w(u,v)) v表示子节点
由上往下递归实现。先递推再求和,因为每个节点状态总是受到子节点影响。

设 g (v) 表示子树之外的最远点距离
有 g(u) = w (u,pre(u)) + max (g(pre[u]),f(v)+w(pre[u],v)) v表示兄弟节点

 先处理g,然后往下递推,因为每一个节点的g总是受父结点影响,所以先处理再递推。

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
#define pb push_back
struct edge{int v,w;edge(int a,int b){v=a,w=b;}};
vector<edge>e[N];
int f[N],g[N],pre[N];
int dfs1(int u,int fa){
    f[u]=0;
    for(int i=0;i<e[u].size();i++){
        int v=e[u][i].v,w=e[u][i].w;
        if(v==fa)continue;
        f[u]=max(f[u],dfs1(v,pre[v]=u)+w);
    }
    return f[u];
}
void dfs2(int u,int fa){
    g[u]=g[fa];
    int t=0;
    for(int i=0;i<e[fa].size();i++){
        int v=e[fa][i].v,w=e[fa][i].w;
        if(v==pre[fa]||v==fa)continue;
        else if(v==u)t=w;
        else g[u]=max(g[u],f[v]+w);
    }   
    g[u]+=t;
    for(int i=0;i<e[u].size();i++){
    int v=e[u][i].v;
    if(v!=fa)dfs2(v,u);
    }

}
int main(){
    int n;
    while(~scanf("%d",&n)){
    for(int i=1;i<=n;i++)e[i].clear();
    // memset(g,0,sizeof g)
    memset(f,0,sizeof f);
    memset(g,0,sizeof g);
    for(int u=2,v,w;u<=n;u++){
        scanf("%d %d",&v,&w);
        e[u].pb(edge(v,w));
        e[v].pb(edge(u,w));
    }
    dfs1(1,0);
    dfs2(1,0);
    for(int i=1;i<=n;i++)printf("%d\n",max(f[i],g[i]));
    }

    return 0;
}
View Code

 

Godfather

 POJ - 3107 

题意:教父这个点,去掉之后,这个树最大联块,最小,求有多少个会是教父。

直接一遍dfs

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N=5e4+5;
struct edge{int v,next;}e[N*10];
int f[N],sz[N],head[N];
int ecnt,n;
void add(int u,int v){
    e[ecnt].v=v,e[ecnt].next=head[u],head[u]=ecnt++;
}
void dfs_size(int u,int fa){
    sz[u]=1;
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==fa)continue;
        dfs_size(v,u);
        sz[u]+=sz[v];
    }
}
void dfs_ans(int u,int fa){
    f[u]=n-sz[u];
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==fa)continue;
        f[u]=max(f[u],sz[v]);
    }
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].v;
        if(v==fa)continue;
        // f[u]=max(f[u],sz[v]);
        dfs_ans(v,u);
    }
}
int main(){
    memset(head,-1,sizeof head);
    ecnt=0;
    scanf("%d",&n);
    for(int i=1,u,v;i<n;i++){
        scanf("%d %d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs_size(1,-1);
    dfs_ans(1,-1);
    int Min=1e9;
    for(int i=1;i<=n;i++)Min=min(Min,f[i]);
    vector<int>v;
    for(int i=1;i<=n;i++)if(f[i]==Min)v.push_back(i);
    for(int i=0;i<v.size();i++)printf("%d ",v[i]);
        puts("");
    // system("pause");
    return 0;
}
View Code

 

 

I - Full Tank?

 POJ - 3635 

 
题意就是说,给你一张图,每个城市都有油价,q次询问,每次问你容量为C,求起点到终点得最小花费。
考虑dp求解,设dp(i,j)表示到达 i 城市,还剩 j 的油量,因为每次只有两种选择,要么加油,要么往前走,
树形dp都面临一个选择问题,
 而且满足最优子结构特点,加一升油最优,那么加两升油也可以最优。
但注意编号从0开始。
 
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
#define pb push_back
const int N=1e4+5;
const int inf=0x3f3f3f3f;
int n,m,ecnt;
int head[N],p[N],dp[N][120];
bool vis[N][120];
int C,S,E;
struct edge{int v,w,next;}e[N*10];
void add(int u,int v,int w){
e[ecnt].v=v;e[ecnt].w=w;e[ecnt].next=head[u];head[u]=ecnt++;
}

struct node{
    int u,cost,oil;
    node(int a,int b,int c){u=a;cost=b;oil=c;}
    bool friend operator<(node a,node b){
        return a.cost>b.cost;
    }
};

void dijkstra(){
    memset(dp,inf,sizeof dp);
    memset(vis,0,sizeof vis);
    priority_queue<node>Q;    
    Q.push(node(S,0,0));dp[S][0]=0;
    while(!Q.empty()){
        node cur=Q.top();Q.pop();
        int u=cur.u,oil=cur.oil,cost=cur.cost;
        vis[u][oil]=1;
        if(u==E){printf("%d\n",cost);return ;}

        if(oil+1<=C&&!vis[u][oil+1]&&dp[u][oil+1]>dp[u][oil]+p[u]){
        dp[u][oil+1]=dp[u][oil]+p[u];
        Q.push(node(u,dp[u][oil+1],oil+1));
        }

        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].v;int w=e[i].w;
            if(oil>=w&&!vis[v][oil-w]&&dp[v][oil-w]>cost){
                dp[v][oil-w]=cost;
                Q.push(node(v,dp[v][oil-w],oil-w));
            }

        }
    }
    puts("impossible");
}

int main(){

    memset(head,-1,sizeof head);ecnt=0;
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++)scanf("%d",&p[i]);
    for(int i=1,u,v,w;i<=m;i++){
        scanf("%d %d %d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    // int q,C,S,E;
    int q;
    scanf("%d",&q);
    while(q--){
    scanf("%d %d %d",&C,&S,&E);
    dijkstra();
    }
    // system("pause");
    return 0;
}
View Code

 

 

简单最短路;

题意:给你一张图,可以把k个边变成0,求最短路。

解法:考虑每一条边都只有两种情况,变成0,和不变成0,设 dis [ i  [[ j ] 表示:取了 j 个0,更新过来最短路。

那么就只有两种情况。变成 0 和 不变成0,

更新最短路,保存等级即可,即可。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1e5+5;
const ll INF=1e14;
const int  inf =0x3f3f3f3f;
int head[N],ecnt,K,n,m;//cnt main里初始化为0,head main初始化为-1
struct edge{int v;long long w,next;}e[N*10];//注意有重边,乘10
struct node{
    int id;ll dis;int lev;//id点,dis距离
    node(int x,ll y,int z){id=x,dis=y;lev=z;}
    friend bool operator<(node a,node b){
    return a.dis>b.dis;//距离小的出队
    }
};
ll  dis[N][15];
bool done[N][15];
void add(int u,int v,ll w){//加边
    e[ecnt].v=v,e[ecnt].w=w,e[ecnt].next=head[u],head[u]=ecnt++;
}
void init(){
    memset(head,-1,sizeof head);
    ecnt=0;
}
void dijkstra(){

    memset(done,0,sizeof done );
    memset(dis,inf,sizeof dis);
    priority_queue<node>Q;
    Q.push(node(1,0,0));dis[1][0]=0;
    // node tmp;
    while(!Q.empty()){
    // tmp=Q.top();Q.pop();
    int u=(Q.top()).id,lv=(Q.top()).lev;Q.pop();
    if(done[u][lv])continue;
    done[u][lv]=1;
    for(int i=head[u];~i;i=e[i].next){

    int v=e[i].v;ll w=e[i].w;
    
    if(dis[v][lv]>dis[u][lv]+w){
    dis[v][lv]=dis[u][lv]+w;
    Q.push(node(v,dis[v][lv],lv));
    }
    
    if(lv<K&&dis[v][lv+1]>dis[u][lv]){
        dis[v][lv+1]=dis[u][lv];
        Q.push(node(v,dis[v][lv+1],lv+1));
    }

    }
    }
       
}
int main(){
    int  t;
    scanf("%d",&t);
    while(t--){
    scanf("%d %d %d",&n,&m,&K);
    init();
    int u,v;ll w;
    for(int i=1;i<=m;i++){
    scanf("%d %d %d",&u,&v,&w);
    add(u,v,w);
    }
    dijkstra();
    ll ans=INF;
    for(int i=0;i<=K;i++)ans=min(ans,dis[n][i]);
    printf("%lld\n",ans);
    }
    // system("pause");

    return 0;
}
View Code

 

 

posted @ 2020-04-12 11:14  无声-黑白  阅读(160)  评论(0编辑  收藏  举报