• [NOI2011]Noi嘉年华


    传送门

    神级dp(反正不是我能想到的思路)

    要求活动都是时间岔开,所以两个会场选择的活动可以分成是一段一段的区间。

    不妨让A选择中间连续的一段区间,B会场选择的是两段剩下的活动。

    时间大但n小,所以可以先离散化。

    num[l][r]表示时间[l,r]的区间内,完整的活动个数。

    活动时间不计开始和结束时间,为了方便处理,我们可以把s计入活动中,把t减一,然后每个活动的时间就不能有交点。

    我们先定义pre[i][j]为从时间1到i(这里的时间都是离散化了的),在一个会场选择j个活动,另一个会场可选的最大活动数。

    那么pre的转移为:

    for(int i=1;i<=tot;++i)
          for(int j=0;j<=num[1][i];++j)
          {
             if(j==0)pre[i][j]=num[1][i];
            for(int k=0;k<=i;++k)
            {
                if(num[1][k]>=j)
                pre[i][j]=max(pre[i][j],pre[k][j]+num[k+1][i]);
                if(j>=num[k+1][i])pre[i][j]=max(pre[i][j],pre[k][j-num[k+1][i]]);
                //枚举断点k 
                //在1~i内选择j个,可在1~k内选择j个,剩下k+1到i里的全分给另一个会场 
                //或者把k+1~i内的活动全部选完,把1~k里多的分给另一个会场 
               }
        }

    定义nextt[i][j]为从时间i到tot(tot是最大的时间),在一个会场选择j个活动,另一个会场可选的最大活动数。

    转移与nextt类似:

    for(int i=tot;i>=1;--i)
          for(int j=0;j<=num[i][tot];++j)
          {
             if(j==0)nextt[i][j]=num[i][tot];
            for(int k=tot+1;k>=i;--k)
            {
                if(num[k][tot]>=j)
                   nextt[i][j]=max(nextt[i][j],nextt[k][j]+num[i][k-1]);
                   if(j>=num[i][k-1])nextt[i][j]=max(nextt[i][j],nextt[k][j-num[i][k-1]]);
               }
        }

    如果没有必须选某一个活动的限制,用pre就可以求出答案:

    int ans=-1;
    for(int i=0;i<=n;++i)
      ans=max(ans,min(i,pre[tot][i]));
    printf("%d
    ",ans);

    如果有限制,我们设f[i][j]为从时间i到j这一段必须选求得的答案。

    不妨让A会场选中这一段,那么它还可以在这段时间的前后再选几个活动,剩下的就是B会场的。

    那么每一次都要枚举x,y,i,j,时间为O(n^4)。

    这里有一个单调性优化:

    我们可以感性理解一下(因为我不会证):

    当A会场在x多选了几个,对应的y就要少选,毕竟求的是两个会场的较小值。

    for(int len=tot;len;len--)
          for(int l=1;l+len-1<=tot;l++)
          {
            int r=l+len-1,y=num[r+1][tot];
            f[l][r]=max(f[l-1][r],f[l][r+1]);//!!!
            for(int x=0; x<=num[1][l-1]; x++)
            {
                while(y>0&&cal(l,r,x,y-1)>=cal(l,r,x,y))y--;//单调性优化 
                f[l][r]=max(f[l][r],cal(l,r,x,y));
            }
          }

    然后贴出完整代码~

    #include<bits/stdc++.h>
    #define N 403//注意范围,每个活动存了始终两个时间 
    #define LL long long
    #define INF 10000
    using namespace std;
    int read()
    {
        int x=0,f=1;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    struct act{
        int s,t,ord,res;
    }w[402];
    int b[N],f[2*N][N],pre[2*N][N],nextt[2*N][N],num[2*N][N],g[2*N][N],F[2*N][N];
    int tot=0,n;
    void init()
    {
        sort(b+1,b+1+tot);
        for(int i=1;i<=n;++i)
        {
            w[i].s=lower_bound(b+1,b+1+tot,w[i].s)-b;
            w[i].t=lower_bound(b+1,b+1+tot,w[i].t)-b;
            for(int l=w[i].s;l;--l)
              for(int r=w[i].t;r<=tot;++r)
                num[l][r]++;
        }
        for(int i=0;i<=tot;++i)
          for(int j=1;j<=n;++j)//前i个时间A选择j个活动后B最多多少个活动,后i个时间A选择j个活动后B最多多少个活动
            pre[i][j]=-INF,nextt[i][j]=-INF;
    }
    int cal(int l,int r,int x,int y)
    {
        return min(x+y+num[l][r],pre[l-1][x]+nextt[r+1][y]);
    }
    void work()
    {
        for(int i=1;i<=tot;++i)
          for(int j=0;j<=num[1][i];++j)
          {
             if(j==0)pre[i][j]=num[1][i];
            for(int k=0;k<=i;++k)
            {
                if(num[1][k]>=j)
                pre[i][j]=max(pre[i][j],pre[k][j]+num[k+1][i]);
                if(j>=num[k+1][i])pre[i][j]=max(pre[i][j],pre[k][j-num[k+1][i]]);
                //枚举断点k 
                //在1~i内选择j个,可在1~k内选择j个,剩下k+1到i里的全分给另一个会场 
                //或者把k+1~i内的活动全部选完,把1~k里多的分给另一个会场 
               }
        }
        for(int i=tot;i>=1;--i)
          for(int j=0;j<=num[i][tot];++j)
          {
             if(j==0)nextt[i][j]=num[i][tot];
            for(int k=tot+1;k>=i;--k)
            {
                if(num[k][tot]>=j)
                   nextt[i][j]=max(nextt[i][j],nextt[k][j]+num[i][k-1]);
                   if(j>=num[i][k-1])nextt[i][j]=max(nextt[i][j],nextt[k][j-num[i][k-1]]);
               }
        }
        int ans=-1;
        for(int i=0;i<=n;++i)
          ans=max(ans,min(i,pre[tot][i]));
        printf("%d
    ",ans);
        for(int len=tot;len;len--)
          for(int l=1;l+len-1<=tot;l++)
          {
            int r=l+len-1,y=num[r+1][tot];
            f[l][r]=max(f[l-1][r],f[l][r+1]);//!!!
            for(int x=0; x<=num[1][l-1]; x++)
            {
                while(y>0&&cal(l,r,x,y-1)>=cal(l,r,x,y))y--;//单调性优化 
                f[l][r]=max(f[l][r],cal(l,r,x,y));
            }
          }
        for(int i=1;i<=n;++i)
          printf("%d
    ",f[w[i].s][w[i].t]);
          
    }
    int main()
    {
        freopen("show.in","r",stdin);
        freopen("show.out","w",stdout);
        
        n=read();
        for(int i=1;i<=n;++i)
        {
            w[i].s=read();
            int t=read();
            w[i].t=w[i].s+t-1;
            b[++tot]=w[i].s;
            b[++tot]=w[i].t;
        }
        init();
        work();
    } 
    View Code

    所以说这是什么神仙dp啊

  • 相关阅读:
    Android 单元测试
    Android 读取和保存文件(手机内置存储器)
    Android 检查是否安装SD卡
    Android 检测网络是否可用
    Android 获取网络链接类型
    Android 中如何使用动画
    Ubuntu 下对ADT 添加别名(alias)
    Docker 配置固定IP及桥接的实现方法(转载)
    macOS下通过docker在局域网成功访问mysql5.6数据库
    MySQL 8.0 Docker使用注解
  • 原文地址:https://www.cnblogs.com/yyys-/p/11514692.html
Copyright © 2020-2023  润新知