树状DP专辑(持续更新)

来源:互联网 发布:mysql 默认数据库路径 编辑:程序博客网 时间:2024/05/21 22:44

POJ 1655 Balancing Act 简单树状DP

http://poj.org/problem?id=1655

这道题目题意是说,任意删除树中的一个点,那么被分成几棵树之后,子树中点数最大的值就是该点的balance,求具有最小Balance的点。

我们用num[i]表示以i为根节点的子树所包含的点的个数,那么该点的balance就是最大那个num[i]值了,比较简单,要是不会就看看代码吧,这道题很简单!

#include <iostream>#include <cstring>#include <cstdlib>#include <cstdio>#define INF 1000000000#define MAX 20020using 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 最优连通子集

http://poj.org/problem?id=1192

这道题的意思就是说给你一棵树,然后每个点有个权值,让你求一棵子树使得这棵子树所包含的点的权值之和最大。

那么这道题明显是一道比较简单的树状DP,我们定义value[i]表示以点i为根节点的子树的最大权值之和,那么转移的过程就和给你一个序列,求最大的子序列的和是一样的,具体的转移过程见代码。

#include <iostream>#include <cstring>#include <cstdlib>#include <cstdio>#define INF 1000000000#define MAX 1010using 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

http://poj.org/problem?id=1155

我们定义状态为dp[u][j]表示以u为根节点的子树所提供给j个叶子节点信号的时候的最大的收益值,那么这个时候的转移就有点想背包了,反正比较好写。这要是一开始没有想到要这样定义状态。如果还是不知道怎么转移的话,请看我的代码:

#include <iostream>#include <cstring>#include <cstdlib>#include <cstdio>#include <vector>#define INF 1000000000#define MAX 3030using 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

http://poj.org/problem?id=1849

这道题是要求两个人走完一棵树所走的最短的距离,那么这个最短的距离就是整棵树的边权的和的两倍剪掉以任意一个节点为根的子树所包含的最大值和次大值,这点想想就能明白。还要注意的一点就是,一个点所表示的最大值和次大值不能来之于同一棵子树,这个只要在向上更新的时候小心一点就行了。

#include <iostream>#include <cstring>#include <cstdlib>#include <cstdio>#include <vector>#include <algorithm>#define MAX 100100using 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

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=3326

这道题是说给你一棵树,每一棵树有一个点权,让你求一棵只有k个节点的子树,使得总的权值之和最大。

我们定义dp[u][i]表示以u为根节点,一共包含i个节点时候的最大权值,那么这个时候就成了背包问题了,只要从子节点逐个的更新父亲节点就行了。但是有一点需要注意,就是我们定义的是以u为根节点的子树,那么我们在之前的更新所选的点是任意的进行背包,最后再加上当前这个u节点进行更新,最后求出一个任意节点为根的时候的k个节点的子树的最大值就行了。

具体见代码:

#include <iostream>#include <cstring>#include <cstdlib>#include <cstdio>#include <vector>#define MAX 110using 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

http://acm.hdu.edu.cn/showproblem.php?pid=1011

这道题题意很坑爹,感觉很戳。从根节点往下可以分成好几路走,还有就是要从一个子节点取到Brain至少要有一个Trooper下去,也就是说要从1开始往上转移,不能是0.注意这两点点就可以了,还有就是m为0的时候输出0,如果按照真实游戏的情况,还是可以理解清楚题意的,不过还是有点坑爹。

具体见代码:

#include <iostream>#include <cstring>#include <cstdlib>#include <cstdio>#include <vector>#define MAX 110using 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;}


原创粉丝点击