1. 程式人生 > >樹狀DP專輯(持續更新)

樹狀DP專輯(持續更新)

POJ 1655 Balancing Act 簡單樹狀DP

這道題目題意是說,任意刪除樹中的一個點,那麼被分成幾棵樹之後,子樹中點數最大的值就是該點的balance,求具有最小Balance的點。

我們用num[i]表示以i為根節點的子樹所包含的點的個數,那麼該點的balance就是最大那個num[i]值了,比較簡單,要是不會就看看程式碼吧,這道題很簡單!

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#define INF 1000000000
#define MAX 20020
using namespace std;
int adj[MAX];
int value[MAX];
typedef struct EDGE
{
    int v,next;
}Edge;
Edge edge[MAX<<1];
int edgeNum;

void AddEdge(int u,int v)
{
    edge[edgeNum].v=v,edge[edgeNum].next=adj[u],adj[u]=edgeNum++;
    edge[edgeNum].v=u,edge[edgeNum].next=adj[v],adj[v]=edgeNum++;
}

bool vis[MAX];
int num[MAX],dp[MAX],n;

int dfs(int u)
{
    int ans=0,sum=0;
    vis[u]=true;
    for(int i=adj[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(!vis[v])
        {
            int t=dfs(v);
            sum+=t;
            if(ans<t)
                ans=t;
        }
    }
    num[u]=sum+1;
    int t = n-num[u];
    if(t>ans)
        ans=t;
    dp[u]=ans;
    return num[u];
}

int main()
{
    int T;
    int a,b;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        memset(adj,-1,sizeof(adj));
        edgeNum=0;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&a,&b);
            AddEdge(a,b);
        }
        memset(vis,false,sizeof(vis));
        dfs(1);
        int ans = INF,index;
        for(int i=1;i<=n;i++)
        {
            if(ans>dp[i])
            {
                ans=dp[i];
                index=i;
            }
        }
        printf("%d %d\n",index,ans);
    }
    return 0;
}


POJ 2378 Tree Cutting

        這道題題意就是說給你一棵樹,然後通過刪除一個點之後,與該點相連的邊都被刪除,那麼這個時候形成了新的分開的快,要使的所有的塊中的點的個數不大於n/2刪點方法。

        簡單樹狀dp,我們使用dp[i]表示以i為根的子樹(包括i自己)的節點的個數,於是我們看刪除點i的時候就是要每一個孩子節點包含的子樹都滿足條件,然後父親節點特判一下就行了。

        程式碼比較簡單,看一下就行了。

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
using namespace std;
const int Max=10100;
int adj[Max],edgeNum,fa[Max],dp[Max];
typedef struct EDGE
{
    int v,next;
}Edge;
Edge edge[2*Max];
void Init()
{
    memset(adj,-1,sizeof(adj));
    edgeNum=0;
}
void AddEdge(int u,int v)
{
    edge[edgeNum].v=v,edge[edgeNum].next=adj[u],adj[u]=edgeNum++;
    edge[edgeNum].v=u,edge[edgeNum].next=adj[v],adj[v]=edgeNum++;
}
int vis[Max];
void InitDp(int u)
{
    dp[u]=1;
    vis[u]=1;
    for(int j=adj[u];j!=-1;j=edge[j].next)
    {
        int v=edge[j].v;
        if(vis[v]==0)
        {
            fa[v]=u;
            InitDp(v);
            dp[u]+=dp[v];
        }
    }
}

void Solve(int n)
{
    int flag;
    int num=0;
    for(int i=1;i<=n;i++)
    {
        flag=1;
        for(int j=adj[i];j!=-1;j=edge[j].next)
        {
            int v=edge[j].v;
            int t;
            if(fa[i]==v)
                t=n-dp[i];
            else
                t=dp[v];
            if(t>(n/2))
            {
                flag=0;
                break;
            }
        }
        if(flag)
        {
            num++;
            printf("%d\n",i);
        }
    }
    if(num==0)
        printf("NONE\n");
}

int main()
{
    int n,a,b;
    while(scanf("%d",&n)!=EOF)
    {
        Init();
        for(int i=1;i<n;i++)
        {
            scanf("%d %d",&a,&b);
            AddEdge(a,b);
        }
        memset(vis,0,sizeof(vis));
        InitDp(1);
        Solve(n);
    }
    return 0;
}

POJ 1192 最優連通子集

這道題的意思就是說給你一棵樹,然後每個點有個權值,讓你求一棵子樹使得這棵子樹所包含的點的權值之和最大。

那麼這道題明顯是一道比較簡單的樹狀DP,我們定義value[i]表示以點i為根節點的子樹的最大權值之和,那麼轉移的過程就和給你一個序列,求最大的子序列的和是一樣的,具體的轉移過程見程式碼。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#define INF 1000000000
#define MAX 1010
using namespace std;
int adj[MAX];
int value[MAX];
typedef struct EDGE
{
    int v,next;
}Edge;
Edge edge[MAX<<1];
int edgeNum;

void AddEdge(int u,int v)
{
    edge[edgeNum].v=v,edge[edgeNum].next=adj[u],adj[u]=edgeNum++;
    edge[edgeNum].v=u,edge[edgeNum].next=adj[v],adj[v]=edgeNum++;
}

typedef struct POINT
{
    int x,y;
}Point;
Point point[MAX];
bool IsConnect(Point a,Point b)
{
    if(abs(a.x-b.x)+abs(a.y-b.y)==1)
        return true;
    else
        return false;
}

bool vis[MAX];

int dfs(int u)
{
    int ans = 0;
    vis[u]=true;
    for(int i=adj[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(vis[v]==false)
        {
            int t = dfs(v);
            if(t>0)
                ans+=t;
        }
    }
    value[u]+=ans;
    return value[u];
}

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        memset(adj,-1,sizeof(adj));
        edgeNum=0;
        for(int i=0;i<n;i++)
        {
            scanf("%d%d%d",&point[i].x,&point[i].y,&value[i]);
        }
        for(int i=0;i<n;i++)
        {
            for(int j=i+1;j<n;j++)
            {
                if(IsConnect(point[i],point[j]))
                {
                    AddEdge(i,j);
                }
            }
        }
        memset(vis,false,sizeof(vis));
        dfs(0);
        int ans = -INF;
        for(int i=0;i<n;i++)
        {
            if(ans<value[i])
                ans=value[i];
        }
        printf("%d\n",ans);
    }
    return 0;
}

POJ 1155 TELE

我們定義狀態為dp[u][j]表示以u為根節點的子樹所提供給j個葉子節點訊號的時候的最大的收益值,那麼這個時候的轉移就有點想揹包了,反正比較好寫。這要是一開始沒有想到要這樣定義狀態。如果還是不知道怎麼轉移的話,請看我的程式碼:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#define INF 1000000000
#define MAX 3030
using namespace std;
int adj[MAX];
int value[MAX];
typedef struct EDGE
{
    int v,c,next;
}Edge;
Edge edge[MAX<<1];
int edgeNum;

void AddEdge(int u,int v,int c)
{
    edge[edgeNum].v=v,edge[edgeNum].next=adj[u],edge[edgeNum].c=c,adj[u]=edgeNum++;
    edge[edgeNum].v=u,edge[edgeNum].next=adj[v],edge[edgeNum].c=c,adj[v]=edgeNum++;
}

bool vis[MAX];
int l,r;
vector<int> dp[MAX];
int tdp[MAX];
vector<int> Get(vector<int> a,vector<int> b)
{
    int aLen=a.size(),bLen=b.size();
    tdp[0]=0;
    for(int i=1;i<=aLen+bLen;i++)
        tdp[i]=-INF;
    for(int i=0;i<aLen;i++)
    {
        for(int j=0;j<bLen;j++)
        {
            tdp[i+j]=max(a[i]+b[j],tdp[i+j]);
        }
    }
    vector<int> ans;
    for(int i=0;i<=aLen+bLen-2;i++)
        ans.push_back(tdp[i]);
    return ans;
}

vector<int> dfs(int u)
{
    if(u>=l&&u<=r)
    {
        dp[u].push_back(0);
        dp[u].push_back(value[u]);
        return dp[u];
    }
    vis[u]=true;
    dp[u].push_back(0);
    for(int i=adj[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        int c=edge[i].c;
        if(vis[v]==false)
        {
            vector<int> temp=dfs(v);
            for(int i=1;i<temp.size();i++)
                temp[i]-=c;
            dp[u]=Get(dp[u],temp);

        }
    }
    return dp[u];
}

int main()
{
    int n,m;
    int a,b;
    int k;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(adj,-1,sizeof(adj));
        memset(vis,false,sizeof(vis));
        memset(value,0,sizeof(value));
        for(int i=1;i<=n;i++)
            dp[i].clear();

        edgeNum=0;
        l=n-m+1,r=n;
        for(int i=1;i<=n-m;i++)
        {
            scanf("%d",&k);
            while(k--)
            {
                scanf("%d%d",&a,&b);
                AddEdge(i,a,b);
            }
        }
        for(int i=n-m+1;i<=n;i++)
            scanf("%d",&value[i]);
        dp[1] = dfs(1);
        int len=dp[1].size();
        int ans;
        for(int i=len-1;i>=0;i--)
        {
            if(dp[1][i]>=0)
            {
                ans=i;
                break;
            }
        }
        printf("%d\n",ans);

    }
    return 0;
}


POJ 1849 Two

這道題是要求兩個人走完一棵樹所走的最短的距離,那麼這個最短的距離就是整棵樹的邊權的和的兩倍剪掉以任意一個節點為根的子樹所包含的最大值和次大值,這點想想就能明白。還要注意的一點就是,一個點所表示的最大值和次大值不能來之於同一棵子樹,這個只要在向上更新的時候小心一點就行了。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <algorithm>
#define MAX 100100
using namespace std;

int adj[MAX];
typedef struct EDGE
{
    int v,c,next;
}Edge;
Edge edge[MAX<<1];
int edgeNum;
int glAns,dp[MAX][2];

bool vis[MAX];

void AddEdge(int u,int v,int c)
{
    edge[edgeNum].v=v,edge[edgeNum].c=c,edge[edgeNum].next=adj[u],adj[u]=edgeNum++;
    edge[edgeNum].v=u,edge[edgeNum].c=c,edge[edgeNum].next=adj[v],adj[v]=edgeNum++;
}

typedef struct NODE
{
    int id,v;
    NODE(){}
    NODE(int tid,int tv)
    {
        id=tid,v=tv;
    }
}Node;

bool cmp(Node x,Node y)
{
    if(x.v>y.v)
        return true;
    else
        return false;
}

int * dfs(int u)
{
    vis[u]=true;
    vector<Node> q;
    for(int i=adj[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        int c=edge[i].c;
        if(!vis[v])
        {
            int *t;
            t=dfs(v);
            t[0]+=c;
            if(t[1]>0)
                t[1]+=c;
            q.push_back(Node(v,t[0]));
            q.push_back(Node(v,t[1]));
        }
    }
    sort(q.begin(),q.end(),cmp);
    if(q.size()>0)
    {
        dp[u][0]=q[0].v;
        for(int i=1;i<q.size();i++)
        {
            if(q[i].id!=q[0].id)
            {
                dp[u][1]=q[i].v;
                break;
            }
        }
    }
    //printf("u %d %d %d\n",u,dp[u][0],dp[u][1]);
    if(dp[u][0]+dp[u][1]>glAns)
        glAns=dp[u][0]+dp[u][1];
    return dp[u];
}

int main()
{
    int n,m;
    int a,b,c;
    int sum;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(adj,-1,sizeof(adj));
        edgeNum=0;
        memset(dp,0,sizeof(dp));
        glAns=0;
        memset(vis,false,sizeof(vis));
        sum=0;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            sum+=(c<<1);
            AddEdge(a,b,c);
        }
        dfs(m);
        //printf("%d %d\n",sum,glAns);
        printf("%d\n",sum-glAns);
    }
    return 0;
}


ZOJ 3201 Tree of Tree

這道題是說給你一棵樹,每一棵樹有一個點權,讓你求一棵只有k個節點的子樹,使得總的權值之和最大。

我們定義dp[u][i]表示以u為根節點,一共包含i個節點時候的最大權值,那麼這個時候就成了揹包問題了,只要從子節點逐個的更新父親節點就行了。但是有一點需要注意,就是我們定義的是以u為根節點的子樹,那麼我們在之前的更新所選的點是任意的進行揹包,最後再加上當前這個u節點進行更新,最後求出一個任意節點為根的時候的k個節點的子樹的最大值就行了。

具體見程式碼:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#define MAX 110
using namespace std;
typedef struct EDGE
{
    int v,next;
}Edge;
Edge edge[MAX<<1];
int adj[MAX],edgeNum;
int value[MAX];
vector<int> dp[MAX];
bool vis[MAX];
void AddEdge(int u,int v)
{
    edge[edgeNum].v=v,edge[edgeNum].next=adj[u],adj[u]=edgeNum++;
    edge[edgeNum].v=u,edge[edgeNum].next=adj[v],adj[v]=edgeNum++;
}

int mid[MAX];
vector<int> Update(vector<int> a,vector<int> b)
{
    int aLen=a.size(),bLen=b.size();
    memset(mid,0,sizeof(mid));
    for(int i=0;i<bLen;i++)
    {
        for(int j=0;j<aLen;j++)
        {
            mid[i+j]=max(mid[i+j],b[i]+a[j]);
        }
    }
    vector<int> ans;
    for(int i=0;i<=aLen+bLen-2;i++)
        ans.push_back(mid[i]);
    return ans;
}

vector<int> dfs(int u)
{
    vis[u]=true;
    dp[u].push_back(0);
    for(int i=adj[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(!vis[v])
        {
            vector<int> t=dfs(v);
            dp[u]=Update(dp[u],t);
        }
    }
    for(int i=0;i<dp[u].size();i++)
    {
        dp[u][i]+=value[u];
    }
    dp[u].insert(dp[u].begin(),0);
    return dp[u];
}

int main()
{
    int n,k;
    int a,b;
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d",&value[i]);
            dp[i].clear();
        }

        memset(adj,-1,sizeof(adj));
        memset(vis,false,sizeof(vis));
        edgeNum=0;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&a,&b);
            AddEdge(a,b);
        }
        dfs(0);
        int ans = 0;
        for(int i=0;i<n;i++)
        {
            if(dp[i].size()>k)
            {
                ans = max(ans,dp[i][k]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

HDU 1011 Starship Troopers

這道題題意很坑爹,感覺很戳。從根節點往下可以分成好幾路走,還有就是要從一個子節點取到Brain至少要有一個Trooper下去,也就是說要從1開始往上轉移,不能是0.注意這兩點點就可以了,還有就是m為0的時候輸出0,如果按照真實遊戲的情況,還是可以理解清楚題意的,不過還是有點坑爹。

具體見程式碼:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#define MAX 110
using namespace std;
int N;
typedef struct EDGE
{
    int v,next;
}Edge;
Edge edge[MAX<<1];
int adj[MAX],edgeNum;
int value[MAX],c[MAX];
int dp[MAX][MAX];
int glAns;
bool vis[MAX];
void AddEdge(int u,int v)
{
    edge[edgeNum].v=v,edge[edgeNum].next=adj[u],adj[u]=edgeNum++;
    edge[edgeNum].v=u,edge[edgeNum].next=adj[v],adj[v]=edgeNum++;
}

int Get(int v)
{
    if(v%20==0)
        return v/=20;
    else
        return v/20+1;
}

void dfs(int u)
{
    vis[u]=true;
    memset(dp[u],0,sizeof(dp[u]));
    for(int i=adj[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(!vis[v])
        {
            dfs(v);
            for(int j=N;j>=0;j--)
            {
                for(int k=1;k<=j;k++)
                {
                    dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
                }
            }
        }
    }
    for(int i=N;i>=c[u];i--)
        dp[u][i]=dp[u][i-c[u]]+value[u];
    for(int i=0;i<c[u];i++)
        dp[u][i]=0;
}

int main()
{
    int n,m;
    int a,b;
    while(scanf("%d%d",&n,&m))
    {
        if(n==-1&&m==-1)
            break;
        N=m;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&c[i],&value[i]);
            c[i]=Get(c[i]);
        }

        memset(adj,-1,sizeof(adj));
        memset(vis,false,sizeof(vis));
        edgeNum=0;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&a,&b);
            AddEdge(a,b);
        }
        if(m==0)
        {
            printf("0\n");
            continue;
        }
        dfs(1);
        printf("%d\n",dp[1][N]);
    }
    return 0;
}