【树DP】UVA12929Aerial Tramway

传送门
题目大意:给定 n 个点 (xi,yi) ,形成一条折线,满足 xi,yi>0 xi<xi+1 yiyi+1 对于两个点,如果它们高度相等,且中间所有点都严格低于这两个点,那么你可以在两点间连一条边。你需要连恰好 m 条边,满足每个点严格低于 k 条边,请最大化所有边的长度和。

神奇的树DP…
将可以连的边先列举出来,然后发现这些边不是包含(一条边覆盖另一条边),就是相互独立。
然后神奇滴联想到树,父亲与儿子之间是包含关系,与兄弟之间相互独立。(感觉有点像A+B Problem那道的玄学理论)QAQ

然而本蒟蒻还是做不来/(ㄒoㄒ)/~~
f[i][j][k] 表示以节点 i 为根的子树中连了 j 条边,点连的边的最大值为 k 。转移方程如下:

f[i][j][max(b,d)]=max(f[soni][k][b]+f[i][kj][d])

如果去枚举这些变量肯定会超时的。
讨论 bd 之间的大小关系,同时维护前缀最大值,这样理论上就可以过了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 510
#define MAXM 40005
using namespace std;
const int inf=1e8;

int n, m, k, cnt, val[MAXN];
struct Node
{
    int x, y;
    bool operator < (const Node &a)const
    {return x<a.x;}
}p[MAXN];

struct Line
{
    int u, v, y;
    Line(){}
    Line(const int a,const int b,const int c){u=a, v=b, y=c;}
    bool operator < (const Line &a)const
    {return abs(u-v)<abs(a.u-a.v);}
}line[MAXN];

struct node
{
    int v, next;
}edge[MAXM];
int adj[MAXN], pos;

inline void add(int u,int v)
{
    edge[pos].v=v, edge[pos].next=adj[u];
    adj[u]=pos;++pos;
}

void makeline()
{
    for(int i=1, flag;i<=n;++i)
        for(int j=i+1;j<=n;++j)
            if(p[i].y==p[j].y)
            {
                flag=1;
                for(int l=i+1;l<j&&flag;++l)if(p[l].y>=p[i].y)flag=0;
                if(flag)
                {
                    line[++cnt]=Line(p[i].x,p[j].x,p[i].y);
                    break;
                }
            }
}

void maketree()
{
    //这棵树可能是一个森林,故虚拟一个根节点0
    memset(adj,-1,sizeof adj), pos=0;
    sort(line+1,line+cnt+1);
    for(int i=1, flag;i<=cnt;++i)
    {
        flag=1;
        for(int j=i+1;j<=cnt;++j)
            if(line[i].y<line[j].y&&line[i].u>line[j].u&&line[i].v<line[j].v)
            {
                flag=0;
                add(j,i);
                break;
            }
        if(flag)add(0,i);
    }
}

int f[MAXN][MAXN][11];
int d[MAXN], tmp[MAXN][MAXN];
int dfs(int u,int fa)
{
    int size=1, v, c;
    for(int p=adj[u];~p;p=edge[p].next)
        if((v=edge[p].v)!=fa)
        {
            d[v]=dfs(v,u);
            size+=d[v];
        }
    //这一部分考虑不选u
    for(int j=0;j<k;++j)
    {
        for(int i=0;i<=m;++i)tmp[0][i]=-inf;
        tmp[0][0]=0;

        //这相当与一个背包,保存一下之前的值以防止错误更新
        c=0;
        for(int p=adj[u];~p;p=edge[p].next)
            if((v=edge[p].v)!=fa)
            {
                ++c;
                for(int i=0;i<=m;++i)tmp[c][i]=tmp[c-1][i];

                //由于保存的是前缀最大值,不需要再枚举变量d
                //tmp[c][l]=max(f[son[u]][k][j]+f[u][l-k][j]);
                for(int t=0;t<=d[v];t++)
                    for(int l=t;l<=min(size,m);l++)
                        tmp[c][l]=max(tmp[c][l],tmp[c-1][l-t]+f[v][t][j]);
            }
        for(int i=0;i<=m;i++)f[u][i][j]=max(f[u][i][j],tmp[c][i]);
    }

    //这一部分考虑选u
    if(u)
    {
        for(int j=1, c;j<k;++j)
        {
            for(int i=0;i<=m;++i)tmp[0][i]=-inf;
            tmp[0][1]=val[u];
            c=0;
            for(int p=adj[u];~p;p=edge[p].next)
                if((v=edge[p].v)!=fa)
                {
                    ++c;
                    for(int i=0;i<=m;++i)tmp[c][i]=tmp[c-1][i];

                    //这一部分和上面很像 但不要忘了这是选了u的 要-1
                    for(int t=0;t<=d[v];t++)
                        for(int l=t+1;l<=min(size,m);l++)
                            tmp[c][l]=max(tmp[c][l],tmp[c-1][l-t]+f[v][t][j-1]);
                }
            for(int i=0;i<=m;++i)f[u][i][j]=max(f[u][i][j],tmp[c][i]);
        }
    }

    for(int i=1;i<=m;i++)
        f[u][i][0]=-inf;
    f[u][0][0]=0;

    //更新前缀最大值
    for(int i=0;i<=m;i++)
        for(int j=1;j<k;j++)
            f[u][i][j]=max(f[u][i][j],f[u][i][j-1]);
    return size;
}

int main()
{
    int cas=0;
    while(~scanf("%d%d%d",&n,&m,&k))
    {
        for(int i=1;i<=n;++i)scanf("%d%d",&p[i].x,&p[i].y);
        cnt=0;
        sort(p+1,p+n+1);
        makeline();
        maketree();

        val[0]=-inf;
        for(int i=1;i<=cnt;++i)val[i]=abs(line[i].u-line[i].v);

        for(int i=0;i<=cnt;++i)
            for(int j=0;j<=m;++j)
                for(int l=0;l<=k;++l)
                    f[i][j][l]=-inf;
        dfs(0,-1);
        //已更新前缀最大值,直接访问f[0][m][k-1]即可
        printf("Case %d: %d\n",++cas,(f[0][m][k-1]>0?f[0][m][k-1]:-1));
    }
    return 0;
}

你可能感兴趣的:(树DP,UVA12929)