樹形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;
}
View Code

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;
}
View Code

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;
}
View Code

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;
}
View Code

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;
}
View Code

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;
}
View Code

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;
}
View Code

8.POJ 1155

題意:樹中每個葉子有點權表示收入,邊權表示花費。選擇某些葉子后,不必要的邊可刪掉,最多選擇幾個點使得花費>=收入

狀態很多時候是根據要求的東西來定義的

這里定義dp[u][i]為子樹u取i個葉子的最優解

由於不選葉子的話是不需要任何花費的,所以容易想到dp[u][0]=0

考慮一個葉子,其第二維只有0/1兩種取值,1的話明顯是dp[u][1]=w[u]

考慮一個非葉子,第二維1~sz[u]都是無法確定的狀態,而且考慮到結果可能是負值,而且我們需要的是一個最大值,所以初始化為-INF

對於一個結點u的一個兒子v,枚舉在這個兒子中取的葉子數k,維護個最優解就好了

#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 = 3e3+5;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

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

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

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

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){
    int f=0;sz[u]=0;dp[u][0]=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        f=1;
        dfs1(v,u);
        sz[u]+=sz[v];
    }
    if(!f){
        sz[u]=1;dp[u][1]=w[u];
    }
    else{
        for(int i=1;i<=sz[u];i++)
            dp[u][i]=-INF;
    }
}

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 j=sz[u];j>=1;j--)
        for(int k=1;k<=sz[v];k++) if(j>=k)
            dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-edge[i].w);
    }
}

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

9.HDU 1011

題意:樹中每一個結點有一個花費一個收益,問K元能取得的最大收益是多少

明顯的樹形背包,一個結點可以看成一個物品

XJB搞搞就行了

有個坑是a/20向上取整不能寫成(a-1)/20+1,a可能是0

#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 = 105;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

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

int tot,n,m;
int first[N],c[N],p[N],dp[N][N];

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

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){
    int x=(c[u]+19)/20;
    for(int i=x;i<=m;i++) dp[u][i]=p[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        for(int j=m;j>=x;j--)
        for(int k=1;k<=j-x;k++)
            dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        if(n==-1&&m==-1) break;
        init();
        for(int i=1;i<=n;i++) scanf("%d%d",&c[i],&p[i]);
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        if(!m){
            puts("0");
            continue;
        }
        dfs1(1,-1);
        printf("%d\n",dp[1][m]);
    }
    return 0;
}
View Code

10.POJ 1947

題意:問最少需要破壞多少條邊能產生一個大小為K的塊

dp[u][i]表示u的子樹中產生一個i大小的塊需要破壞的最少邊數

num[u]表示u的兒子數

這題考慮的方向不同平常

考慮塊的大小1~sz[u] (0的大小是不合法的狀態)

若要在u的子樹中產生一個1大小的塊,初始情況應該是u和所有兒子的連邊斷開,所以dp[u][1]=num[u]

其實這樣處理相當於對於每一個狀態來說u都是默認取了的

考慮一個結點u及其一個兒子v,對於狀態dp[u][i] 枚舉在v中取的結點數k

最后要枚舉一個塊的根,因為我們之前處理狀態的時候是默認根取,所以最優解還可能在子樹中

如果加一維狀態[0/1]表示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
#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 = 155;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

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

int tot,n,m;
int first[N],dp[N][N],sz[N],num[N];

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

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;num[u]=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        num[u]++;
        sz[u]+=sz[v];
    }
}

void dfs2(int u,int fa){
    dp[u][1]=num[u];
    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 j=m;j>=1;j--)
        for(int k=1;k<=sz[v];k++)
           if(j-k>=1) dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-1);

    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init();
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        /*for(int i=1;i<=n;i++){
            cout<<i<<":"<<endl;
            for(int j=1;j<=sz[i];j++) cout<<dp[i][j]<<' ';
            cout<<endl;
        }*/
        int ans=dp[1][m];
        for(int i=2;i<=n;i++) ans=min(ans,dp[i][m]+1);
        printf("%d\n",ans);
    }
    return 0;
}
View Code

11.HDU 1561

題意:每個結點有一個權值,問選擇K個結點(必須按路徑選擇)獲得的最大權值

典型的樹形背包

由於不是任意選,我們只需要在狀態轉移的時候變通一下就好了

對於u來說,我要選擇其子孫的話前提是u選擇

所以對第二維i==1的時候單獨處理,而且之后不再更新這個值就能保證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
#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 = 205;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

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

int tot,n,m;
int first[N],dp[N][N],w[N];

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

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 dfs(int u){
    dp[u][1]=w[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        dfs(v);
        for(int j=m;j>=2;j--)
        for(int k=0;k<=m;k++)
            if(j-1>=k) dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    w[0]=0;
    while(scanf("%d%d",&n,&m)&&(n||m)){
        init();m++;
        int rt=0,u;
        for(int i=1;i<=n;i++){
            scanf("%d%d",&u,&w[i]);
            addedge(u,i);
        }
        dfs(rt);
        printf("%d\n",dp[rt][m]);
    }
    return 0;
}
View Code

12.HDU 4003

題意:樹中每條邊有花費,有k個機器人,問遍歷所有的點的最少花費(可以回頭,每次只能從s出發

這一題的狀態定義很有意思

dp[u][i]表示遍歷完u的子樹后有i個機器人沒有回到u結點的最優解

對於每一棵子樹都是需要遍歷完的,所以必須選擇一個方案

為了保證至少選擇一個方案,所以在考慮當前兒子v的時候先將答案加上一個狀態,這里是加上沒有機器人留在v子樹的方案

然后再對v做一個背包,若有更優解則初始放置的選擇將被替換掉

其實相當於對於子樹v有k件物品來選擇,必須選擇一件

#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 = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

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

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

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

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++;
}

int n,s,m;

void dfs(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=m;j>=0;j--){
            dp[u][j]+=dp[v][0]+edge[i].w*2;
            for(int k=1;k<=j;k++)
                dp[u][j]=min(dp[u][j],dp[v][k]+dp[u][j-k]+edge[i].w*k);
        }
    }
}

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

13.HDU 4276

題意:每個點有點權,邊有花費,問T時間能否從1到達n,若能,能獲取的最大權值是多少

首先想到樹中兩點的路徑是唯一的,既然要從1到達n,先考慮最短路徑能否到達

之后剩余的時間作為背包容量,因為其余的點都是非必須的,所以去了必須還要回來,故每條非必要的邊花費都需要*2

 所以做法就是先把總時間減去1到n的路徑距離,並把路徑上的花費置零

然后對於每一個結點做背包,對於u的兒子v枚舉一個花費k,注意邊的花費要double

#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 = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

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

int tot;
int first[N],dp[N][N*5],pre[N],id[N],w[N],inq[N],dis[N];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,0);
    mems(inq,0);
    mems(dis,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++;
}

int n,m,cnt;
queue<int>q;

void bfs(int s){
    while(!q.empty()) q.pop();
    pre[s]=1;dis[s]=0;
    inq[s]=1;
    q.push(s);
    while(!q.empty()){
        int u=q.front();q.pop();inq[u]=0;
        if(u==n) break;
        for(int i=first[u];i!=-1;i=edge[i].next){
            int v=edge[i].e;
            if(!inq[v]&&dis[u]+edge[i].w<dis[v]){
                dis[v]=dis[u]+edge[i].w;
                q.push(v);
                inq[u]=1;
                pre[v]=u;
                id[v]=i;
            }
        }
    }
    cnt=0;
    int u=n;
    while(u!=1){
        cnt+=edge[id[u]].w;
        edge[id[u]].w=edge[id[u]^1].w=0;
        u=pre[u];
    }
}

void dfs2(int u,int fa){
    for(int i=0;i<=m;i++) dp[u][i]=w[u];
    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 j=m;j>=0;j--)
        for(int k=0;k<=j;k++) if(j-edge[i].w*2-k>=0)
            dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-edge[i].w*2-k]);
    }
}

int u,v,W;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&W);
            addedge(u,v,W);
        }
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        bfs(1);
        if(cnt>m){
            printf("Human beings die in pursuit of wealth, and birds die in pursuit of food!\n");
            continue;
        }
        m-=cnt;
        dfs2(1,-1);
        /*for(int i=1;i<=n;i++){
            cout<<i<<':'<<endl;
            for(int j=0;j<=m;j++) cout<<dp[i][j]<<' ';cout<<endl;
        }*/
        printf("%d\n",dp[1][m]);
    }
    return 0;
}
View Code

14.HDU 3586

題意:給一個限制m,切斷的路徑權值和不超過m,單個邊權值也不超過k,求最小的k使得所有葉子和根不相連

二分一個k

對於一個確定的k,dp[u]表示u的葉子全部和u分離需要的最小花費

考慮葉子節點:dp[u]=INF(不合法狀態

考慮非葉子結點:一開始是沒有和兒子相連,所以dp[u]=0

考慮u的一個兒子v,若(u,v)這條邊是<=lim,則可以選擇在消除這條邊或者是在v的子樹中消除,兩者取一個最優解

最后判斷dp[1]是否小於給定的m

#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 = 1005;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f;

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

int tot,n,m;
int first[N],dp[N];

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 dfs(int u,int fa,int lim){
    dp[u]=0;int f=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u,lim);f=1;
        int tmp;
        if(edge[i].w<=lim) tmp=edge[i].w;
        else tmp=INF;
        dp[u]+=min(tmp,dp[v]);
    }
    if(!f) dp[u]=INF;
}

bool check(int mid){
    mems(dp,INF);
    dfs(1,-1,mid);
    return dp[1]<=m;
}

int u,v,w;
int main(){
    //freopen("in.txt","r",stdin);
    while(scanf("%d%d",&n,&m)&&(n||m)){
        init();
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }
        int low=1,high=INF,mid,ans=-1;
        while(low<=high){
            mid=(low+high)>>1;
            if(check(mid)){
                ans=mid;
                high=mid-1;
            }
            else low=mid+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

15. POJ 3107

題意:輸出樹中的結點k,刪去k后產生的最大的聯通塊最小

直接兩次dp就好了,第一次處理子樹第二次考慮父親除去當前結點產生的最大塊,維護個最大值

#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 = 50005;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N<<1];

int tot,n,m;
int first[N],dp[N],sz[N];
vector<int> ans;
void init(){
    tot=0;
    ans.clear();
    mems(first,-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;dp[u]=0;
    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]=max(dp[u],sz[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;
        dp[v]=max(dp[v],n-sz[v]);
        dfs2(v,u);
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        int cnt=INF;
        for(int i=1;i<=n;i++) cnt=min(cnt,dp[i]);
        //for(int i=1;i<=n;i++) cout<<i<<' '<<dp[i]<<endl;
        for(int i=1;i<=n;i++) if(dp[i]==cnt) ans.push_back(i);
        for(int i=0;i<ans.size();i++){
            if(i) printf(" ");
            printf("%d",ans[i]);
        }
        puts("");
    }
    return 0;
}
View Code

16.POJ 3140

題意:選擇一條樹邊斷開,使得分成的兩部分的總點權差最小,輸出最小值

就直接預處理每一個點及其子樹的總點權

枚舉一個點和其父親斷開,取個最優值就好了

#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 = 1e5+5;
const int M = N<<1;
const LL MOD = 998244353;
const LL INF = 1e18;

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

int tot,n,m;
int first[N];
LL sz[N],num[N],cnt;

void init(){
    tot=0;cnt=0;
    mems(first,-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 dfs(int u,int fa){
    sz[u]=num[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        sz[u]+=sz[v];
    }
}
int u,v,cas=1;
int main(){
    //freopen("in.txt","r",stdin);
    while(scanf("%d%d",&n,&m)&&(n||m)){
        init();
        for(int i=1;i<=n;i++) scanf("%lld",&num[i]),cnt+=num[i];
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs(1,-1);
        LL ans=INF;
        for(int i=2;i<=n;i++){
            LL tmp=cnt-sz[i]*2;
            if(tmp<0) tmp*=-1;
            ans=min(ans,tmp);
        }
        printf("Case %d: %lld\n",cas++,ans);
    }
    return 0;
}
View Code

17. POJ 2486

題意:從樹根1走K步能獲得的最大點權,可以走回頭路

dp[u][i][0/1]表示從u出發走i步,最終回到u/不回到u的最優值

考慮一個葉子結點:dp[u][0][0]=w[u];

考慮一個非葉子結點u及其一個兒子v,枚舉一個k表示對於這個兒子v走的步數

有3種結果:留在v子樹,回到u,留在u其它子樹

畫圖就能看出三種情況的轉移:

留在v子樹:狀態分裂為dp[u][j-k-1][0],dp[v][k][1]  花費為1步

回到u:狀態分裂為dp[u][j-k-2][0],dp[v][k][0] 花費為2步

留在其他子樹:狀態分裂為dp[u][j-k-2][1],dp[v][k][0] 花費為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 = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N<<1];

int tot;
int first[N],dp[N][N<<1][2],w[N];

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

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

int n,m;

void dfs(int u,int fa){
    for(int i=0;i<=m;i++) dp[u][i][0]=dp[u][i][1]=w[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=m;j>=1;j--)
        for(int k=0;k<=j;k++){
            if(j>=k+2) dp[u][j][0]=max(dp[u][j][0],dp[v][j-k-2][0]+dp[u][k][0]);
            if(j>=k+1) dp[u][j][1]=max(dp[u][j][1],dp[v][j-k-1][1]+dp[u][k][0]);
            if(j>=k+2) dp[u][j][1]=max(dp[u][j][1],dp[v][j-k-2][0]+dp[u][k][1]);
        }
    }
}

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

18. HDU 4044

題意:每個點有ki種選擇,每種選擇對應一種一種花費一種收益 問擁有m元,令x為1到所有葉子(不含1)的路徑的點權和最小值

求x的最大值

一開始的想法是的定義dp[u][i][0/1]為u子樹花i元,u選或者不選的答案

但是后面會發現這樣定義狀態的話對於dp[u][i][1]的合並不是很好處理,因為我沒有記錄u選擇的是哪個方案

看了題解后學了一個新姿勢

定義dp[u][i]為u不選的時候花i元的最優解

預處理一個w[u][i]表示u結點花i元最多能獲得的權值

考慮一個葉子結點:dp[u][i]=w[u][i]

考慮一個非葉子結點不選時候:每一個兒子v加進來的時候,狀態都分裂為dp[v][k]和dp[u][j-k]

對於每一個枚舉的k來說,答案是min(dp[v][k],dp[u][j-k]) 維護這個答案的最大值就是dp[u][j]

考慮完u不選的情況后,再對u單獨做一次背包,枚舉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 = 1e3+5;
const int M = 205;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

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

int tot;
int first[N],dp[N][M],kind[N],chose[N][M];

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

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

int T,n,m;

void dfs1(int u,int fa){
    int f=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        f++;
        for(int j=m;j>=0;j--){
            int tmp=0;
            for(int k=0;k<=j;k++) tmp=max(tmp,min(dp[u][j-k],dp[v][k]));
            dp[u][j]=tmp;
        }
    }
    if(!f){
        for(int i=0;i<=m;i++) dp[u][i]=chose[u][i];
        return;
    }
    for(int j=m;j>=0;j--)
    for(int k=0;k<=j;k++) dp[u][j]=max(dp[u][j],dp[u][j-k]+chose[u][k]);
}

int main(){
    //freopen("in.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&n);
        int u,v,k;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        scanf("%d",&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&k);
            for(int j=1;j<=k;j++){
                scanf("%d%d",&u,&v);
                chose[i][u]=max(chose[i][u],v);
            }
            for(int j=1;j<=m;j++) chose[i][j]=max(chose[i][j],chose[i][j-1]);
        }
        dfs1(1,-1);
        printf("%d\n",dp[1][m]);
    }
    return 0;
}
View Code

 19.HDU 5758

題意:點有點權邊有花費,點權只能獲得一次,花費每次經過都要扣除,可以走回頭路,問能拿到的最大價值

參考Apple Tree可以知道需要加一維[0/1]表示是否回到u

定義dp[u][0/1]表示從u出發不回/回到u的最優解

求一個結點對於整棵樹的信息一般都是先處理子樹內的信息,再第二次dfs處理父親對答案的影響

先考慮子樹內:

考慮一個葉子結點:dp[u][0]=dp[u][1]=w[u]

考慮一個非葉子u和他的一個兒子v:

對於dp[u][0]來說,v的貢獻只能是dp[v][0]-2*w[u,v],如果這個值小於0我必然不走v

對於dp[u][1]來說,如果不停在v內,v的貢獻也是dp[v][0]-2*w[u,v],如果停在u則狀態分裂為dp[u][0]+dp[v][1]-w[u,v]

再考慮fa對u的影響

考慮fa對u的影響時一般需要把u對fa的影響先排除

對於dp[fa][0]來說u的影響只能是max(0,dp[u][0]-2*w[fa,u]),直接減去就行了

對於dp[fa][1]來說有兩種情況

若最終不停在u,則和dp[fa][0]一樣處理

若停在u,則需要對fa再做一次排除u后的背包

這里將fa對u的影響作為參數下傳,為的是保證在推u的時候fa的值是對於整棵樹的,而其余結點是對於其子樹的

之所以這樣做是因為用fa更新u的時候,排除u對fa的影響之后fa相當於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 = 1e5+50;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

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

int tot,n,m;
int first[N],dp[N][2],w[N],id[N];

void init(){
    tot=0;
    for(int i=1;i<=n;i++) first[i]=-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){
    dp[u][0]=dp[u][1]=w[u];
    id[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);
        int tmp=max(0,dp[v][0]-2*edge[i].w);
        dp[u][1]+=tmp;
        if(dp[u][1]<dp[u][0]+max(0,dp[v][1]-edge[i].w)){
            dp[u][1]=dp[u][0]+max(0,dp[v][1]-edge[i].w);
            id[u]=v;
        }
        dp[u][0]+=tmp;
    }
}
int ans[N];
void dfs2(int u,int fa,int f0,int f1){
    int t[2]={dp[u][0],dp[u][1]};
    int idd=id[u];
    t[1]+=f0;
    if(t[1]<t[0]+f1){
        t[1]=t[0]+f1;
        idd=fa;
    }
    t[0]+=f0;
    ans[u]=max(t[0],t[1]);

    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        if(v==idd){
            int tmp0=w[u]+f0,tmp1=w[u]+f1;
            for(int k=first[u];k!=-1;k=edge[k].next){
                int vv=edge[k].e;
                if(vv==fa||vv==v) continue;
                int tmp=max(0,dp[vv][0]-2*edge[k].w);
                tmp1+=tmp;
                tmp1=max(tmp1,tmp0+max(0,dp[vv][1]-edge[k].w));
                tmp0+=tmp;
            }
            tmp0=max(0,tmp0-2*edge[i].w);
            tmp1=max(0,tmp1-edge[i].w);
            dfs2(v,u,tmp0,tmp1);
        }
        else{
            int tmp=max(0,dp[v][0]-2*edge[i].w);
            int tmp0=max(0,t[0]-tmp-2*edge[i].w);
            int tmp1=max(0,t[1]-tmp-edge[i].w);
            dfs2(v,u,tmp0,tmp1);
        }
    }
}

int T,u,v,W,cas=1;

int main(){
    //freopen("in.txt","r",stdin);
    //freopen("pending.txt","w",stdout);
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        init();
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&W);
            addedge(u,v,W);
        }
        dfs1(1,-1);
        dfs2(1,-1,0,0);
        printf("Case #%d:\n",cas++);
        for(int i=1;i<=n;i++) 
            printf("%d\n",ans[i]);
    }
    return 0;
}
View Code

 20.NUBT 1638

題意:建圖略麻煩,抽象出來就是說樹中選m個結點,其中rt到每一個葉子的路徑上被選的結點數不超過k,問獲得的最大權值是多少

定義dp[u][i][j][0/1]表示u子樹選i個,最多的路徑選了j個,u選/不選

dp值全部初始化為-1表示不合法狀態

考慮一個葉子結點:選的話是dp[u][1][1][1]=w[u]  不選dp[u][0][0][0]=0

考慮一個非葉子結點u和他的一個兒子v:

對於狀態dp[u][i][j][0],可能轉移過來的狀態有dp[u][i-k][j][0]+dp[v][k][0~j][0/1]或者dp[u][i-k][0~j][0]+dp[v][k][j][0/1]

對於狀態dp[u][i][j][1],有dp[u][i-k][j][1]+dp[v][k][0~j-1][0/1]或dp[u][i-k][0~j][1]+dp[v][k][j-1][0/1](之所以v是j-1為上限是因為u選了的話合並后v的最長鏈長度必然+1,這樣做是為了限制最長鏈在j范圍內)

#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 = 120;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct Point{
    int x,y;
}p[N<<2];

bool cmp(Point a,Point b){
    return a.x<b.x;
}

struct Node{
    int y,l,r;
}point[N];

bool cmp1(Node a,Node b){
    if(a.y==b.y) return a.l<b.l;
    return a.y>b.y;
}

struct node{
    int e,next;
}edge[N<<1];

int first[N],cnt,tot;
int dp[N][N][11][2],w[N],sz[N];

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

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

void build(int l,int r,int fa){
    for(int i=l+1;i<=r;i++){
        if(p[l].y==p[i].y){
            addedge(fa,++cnt);
            w[cnt]=p[i].x-p[l].x;
            //cout<<fa<<' '<<cnt<<' '<<w[cnt]<<endl;
            build(l+1,i-1,cnt);
            build(i,r,fa);
            return ;
        }
        else if(p[i].y>p[l].y){
            build(l+1,i-1,fa);
            build(i,r,fa);
        }
    }
}

int n,m,K;

void dfs(int u){
    dp[u][1][1][1]=w[u];
    dp[u][0][0][0]=0;

    sz[u]=1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        dfs(v);
        sz[u]+=sz[v];
        for(int j=sz[u];j>=0;j--)
        for(int k=0;k<=K;k++)
        for(int p=0;p<=min(sz[v],j);p++)
        for(int kk=0;kk<=k;kk++){
            if(dp[u][j-p][k][0]!=-1&&(dp[v][p][kk][0]!=-1||dp[v][p][kk][1]!=-1))
            dp[u][j][k][0]=max(dp[u][j][k][0],dp[u][j-p][k][0]+max(dp[v][p][kk][0],dp[v][p][kk][1]));

            if(dp[u][j-p][kk][0]!=-1&&(dp[v][p][k][0]!=-1||dp[v][p][k][1]!=-1))
            dp[u][j][k][0]=max(dp[u][j][k][0],dp[u][j-p][kk][0]+max(dp[v][p][k][0],dp[v][p][k][1]));

            if(kk>=1&&dp[u][j-p][k][1]!=-1&&(dp[v][p][kk-1][0]!=-1||dp[v][p][kk-1][1]!=-1))
            dp[u][j][k][1]=max(dp[u][j][k][1],dp[u][j-p][k][1]+max(dp[v][p][kk-1][0],dp[v][p][kk-1][1]));

            if(k>=1&&dp[u][j-p][kk][1]!=-1&&(dp[v][p][k-1][0]!=-1||dp[v][p][k-1][1]!=-1))
            dp[u][j][k][1]=max(dp[u][j][k][1],dp[u][j-p][kk][1]+max(dp[v][p][k-1][0],dp[v][p][k-1][1]));

        }
    }
}

int cas=1;

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d%d",&n,&m,&K)){
        init();
        for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y);
        sort(p+1,p+1+n,cmp);
        build(1,n,0);

        int rt=0;
        dfs(rt);
        int ans=-1;
        for(int i=0;i<K;i++) ans=max(ans,dp[rt][m][i][0]);
        printf("Case %d: %d\n",cas++,ans);
    }
    return 0;
}
View Code

21.HDU 5758

題意:邊花費1,可以從一個點瞬移到另一個點,問瞬移次數最少的前提下遍歷所有邊的最小花費

可以知道瞬移次數是(葉子數+1)/2

定義dp[u][0]為遍歷完u后的最小花費(只是第一次dp,答案不一定是最終答案

考慮非葉子u和他的一個兒子v:

若v有偶數個兒子,則要在瞬移次數最少的情況下遍歷(u,v)這條邊,就必須有兩個結點和v外的結點配對,所以(u,v)對答案貢獻2

同理奇數的時候貢獻1

如果總葉子數是偶數那答案已經出來了

但是如果總葉子數是奇數這樣的答案可能會 偏大

其實奇數的情況就是在偶數的情況加了一條沒有分叉的單鏈

所以再定義dp[u][1]為遍歷u后的最小花費(第二次dp,獨立與第一次dp

對於一個u,枚舉這條單鏈出現的兒子v:

如果這個兒子就是一條單鏈,並且u是這條單鏈的起點,則dp[u][1]=min(dp[u][1],dp[u][0])

如果不是兒子v不是單鏈則把這條單鏈從v中剔除,則v中的葉子數的奇偶就變化了

dp[u][1]先減去dp[v][0],再減去奇偶變化的影響d,再加上單鏈存在與v的dp值dp[v][1],維護最優解

最后若總葉子數是偶數則取第一次dp的結果,否則取第二次的

#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 = 1e5+5;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next;
}edge[N<<1];

int first[N],cnt,tot;
LL dp[N][2];
int sz[N],deg[N];

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

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 dfs(int u,int fa){
    dp[u][0]=0;sz[u]=0;
    int flag=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        flag++;
        dfs(v,u);
        sz[u]+=sz[v];
        dp[u][0]+=dp[v][0];
        if(sz[v]&1) dp[u][0]++;
        else dp[u][0]+=2;
    }
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        if(flag>1&&sz[v]==1) dp[u][1]=min(dp[u][1],dp[u][0]);
        if(dp[v][1]==INF) continue;
        LL tmp=dp[u][0]-dp[v][0]+dp[v][1];
        if(sz[v]&1) tmp++;
        else tmp--;
        dp[u][1]=min(dp[u][1],tmp);
    }
    if(!flag) sz[u]=1;
}

int n,m,u,v,T;

int main(){
    //freopen("in.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&n);
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
            deg[v]++;deg[u]++;
        }
        int rt=1,cnt=0;
        for(int i=1;i<=n;i++) {
            if(deg[i]!=1)
                rt=i;
            else cnt++;
        }
        dfs(rt,-1);
        printf("%lld\n",dp[rt][cnt&1]);
    }
    return 0;
}
View Code

 22.codeforces Round #322(Div.2) F

題意:把葉子平分染成兩種顏色,其余點隨便染,求最少有對相鄰的點顏色不同  輸入保證葉子是偶數個

定義dp[u][i][0/1]表示u子樹染i個兒子0色,u染0/1色

這題經典的地方在於葉子和非葉子的初始情況不一樣

先dp值全部賦INF表示不合法

對於葉子來說:dp[u][1][0]=0,dp[u][0][1]=0,其余兩個狀態不合法

考慮非葉子u:

非葉子u的顏色對第二維是沒影響的,所以dp[u][0][1]=dp[u][0][0]=0

由於這里是必須要選擇兒子的情況,之前有些題目是兒子可以不選

必須選的情況的處理方法有兩種,第一種是先強制放一種狀態,再枚舉其他狀態看看是否更優,有就替換,沒有就保留原來的

或者說是用一個變量去存放兒子的最優情況,然后把這個最優情況強制合並到原狀態里

這里我采用的是第二種方法

#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 = 5005;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next;
}edge[N<<1];

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

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

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

int n,u,v;

void dfs(int u,int fa){
    dp[u][0][1]=0;
    dp[u][0][0]=0;
    leaf[u]=0;
    int flag=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        flag=1;
        leaf[u]+=leaf[v];
        for(int j=leaf[u];j>=0;j--){
            int miv[2]={INF,INF};
            for(int k=0;k<=min(leaf[v],j);k++){
                miv[0]=min(miv[0],min(dp[u][j-k][0]+dp[v][k][1]+1,dp[u][j-k][0]+dp[v][k][0]));
                miv[1]=min(miv[1],min(dp[u][j-k][1]+dp[v][k][1],dp[u][j-k][1]+dp[v][k][0]+1));
            }
            dp[u][j][0]=miv[0];
            dp[u][j][1]=miv[1];
        }
    }
    if(!flag){
        leaf[u]=1;
        dp[u][0][0]=INF;
        dp[u][1][0]=0;
    }
}

int main(){

    init();
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
        deg[v]++;deg[u]++;
    }
    if(n==2) return 0*puts("1");
    int rt,cnt=0;
    for(int i=1;i<=n;i++){
        if(deg[i]!=1) rt=i;
        else cnt++;
    }
    dfs(rt,-1);
    //for(int i=1;i<=n;i++)
    //for(int j=0;j<=leaf[i];j++) printf("i_%d j_%d %d %d\n",i,j,dp[i][j][0],dp[i][j][1]);
    printf("%d\n",min(dp[rt][cnt/2][0],dp[rt][cnt/2][1]));
    return 0;
}
View Code

23.COJ 1793

題意:對於每個點給一個限制xi表示xi選了才能選i,現在最多選m個人,問在這些限制下最多能選幾個

如果連邊(xi->i)一眼看過去結構很像樹,因為每個結點只有一個父親

但是這里可能會構成強聯通分量

不過很容易想到把強聯通分量縮點后也是可以構成樹或者森林的

森林我們可以建立一個虛根0來合並成一棵樹

一開始有個地方沒想明白,就是對於一個強聯通分量來說可能去掉一個點后還是強聯通分量,所以一個強聯通分量內我選擇幾個人是不好判斷的

但是仔細想想題目給的條件,每個點的入度只能為1,而這樣構成的強聯通分量只能是一個簡單環

而對於一個簡單環來說我只能全取或者全不取

所以問題就轉化為了一個m的背包,每個點的sz即使花費也是價值,問能拿到的最大價值是多少

這里有個地方需要變通

這里是選了父親才能選擇兒子,所以對於一個結點u以及他的兒子v來說,我要把v加進來我首先得保證u被選

所以我枚舉v的容量k的時候始終保證j-k也就是剩余的容量始終是大於sz[u]的,這樣就能保證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 = 1e3+5;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;
 
struct node{
    int s,e,next;
}edge[M];
 
int tot,id;
int first[N];
int block,tp,n,m;
int low[N],dfn[N],ins[N],deg[N];
int st[N],belong[N],sz[N];
int dp[N][N];
 
void init(int n){
    tot=0;tp=0;block=0;id=0;
    for(int i=0;i<=n;i++){
        first[i]=-1;
        dfn[i]=0;
        ins[i]=0;
        deg[i]=0;
        for(int j=0;j<=m;j++) dp[i][j]=0;
    }
}
 
void addedge(int u,int v){
    edge[tot]=(node){u,v,first[u]};
    first[u]=tot++;
    //edge[tot]=(node){u,first[v]};
    //first[v]=tot++;
}
 
void tarjan(int u){
    low[u]=dfn[u]=++id;
    ins[u]=1;
    st[++tp]=u;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(ins[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        block++;
        sz[block]=0;
        int v;
        do{
            v=st[tp--];
            ins[v]=0;
            belong[v]=block;
            sz[block]++;
        }
        while(v!=u);
    }
}
 
void rebuild(){
    int tot2=tot;
    tot=0;
    for(int i=0;i<=n;i++) first[i]=-1;
    for(int i=0;i<tot2;i++){
        int u=belong[edge[i].s];
        int v=belong[edge[i].e];
        if(u==v) continue;
        deg[v]++;
        addedge(u,v);
    }
}
 
void dfs(int u,int fa){
    for(int i=sz[u];i<=m;i++) dp[u][i]=sz[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=m;j>=sz[u];j--)
        for(int k=0;k<=j-sz[u];k++)
            dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k]);
    }
}
int u;
 
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init(n);
        for(int i=1;i<=n;i++){
            scanf("%d",&u);
            addedge(u,i);
        }
        for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
        rebuild();
        int rt=0;sz[0]=0;
        for(int i=1;i<=block;i++) if(!deg[i]) addedge(rt,i);
        dfs(rt,-1);
        printf("%d\n",dp[rt][m]==-1?0:dp[rt][m]);
    }
    return 0;
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM