• NOI2011 Noi嘉年华(神级dp)


    题目

    分析:

    拿到题,注意到数据范围很小,又是求最大值,可以往dp那方面想一下。

    难点在于,如何使AB会场的分配尽量均匀,且活动多。考虑固定A选的活动数,去求B选的最多活动数。为了使时间不交叉,将时间压成一维。

    预处理每一个区间中对应的活动数sum[ i ][ j ]。

    定义:dp[ i ][ j ]表示1~i 的时间中,A会场选j个,B会场最多选的个数。

    转移:为了保证不交叉,模拟区间转移的方法:枚举断点k,大区间由小区间转移过来。

    1.dp[ k ][ j ]+sum[ k ][ i ];

    A会场只在1~k的时间里选j个,后面的全部留给B,前面的也留一些给B。

    2.dp[ k ][ j-sum[ k ][ i ] ];

    A会场从前面选 j-sum[ k ][ i ] 个,再从后面选 sum[ k ][ i ] 。

    这样就可以不重不漏地考虑完所有情况了。

    题目中还要求强制性选择,就加一个ansss[ l ][ r ]数组,表示强制选择l~r区间的最大值,转移见代码。

    #include<bits/stdc++.h>
    using namespace std;
    #define ri register int
    #define N 405
    int n,l[N],r[N],tmp[N],u[N],v[N],ls[N],pre[N][N],aft[N][N],sum[N][N],anss[N];
    int main()
    {
        freopen("show.in","r",stdin);
        freopen("show.out","w",stdout);
        int cnt=0,t;
        scanf("%d",&n);
        for(ri i=1;i<=n;++i){
            scanf("%d%d",&l[i],&r[i]);
            r[i]+=l[i];
            tmp[++cnt]=l[i],tmp[++cnt]=r[i];
        } 
        sort(tmp+1,tmp+1+cnt);
        ls[1]=1;//!!!保证从1开始 因为后面的时间点都是从1开始枚举的 
        for(ri i=2;i<=cnt;++i)
        if(tmp[i]!=tmp[i-1]) ls[i]=ls[i-1]+1;//离散化 
        else ls[i]=ls[i-1];
        
        for(ri i=1;i<=n;++i)
         for(ri j=1;j<=cnt;++j){
            if(l[i]==tmp[j]) u[i]=ls[j];
            if(r[i]==tmp[j]) v[i]=ls[j];
        }
        t=ls[cnt];
        for(ri i=1;i<=t;++i)
         for(ri j=i;j<=t;++j)
          for(ri k=1;k<=n;++k){
              if(u[k]>=i && v[k]<=j) sum[i][j]++;
        }
        int inf=1000;
        for(ri i=1;i<=t;++i)
         for(ri j=1;j<=n;++j) 
          pre[i][j]=aft[i][j]=-inf;
        
        for(ri i=1;i<=t;++i)
         for(ri j=0;j<=sum[1][i];++j){
             if(j==0) { pre[i][j]=sum[1][i]; continue; }
             //先初始化 令前i的时间选0个给A会场 = 把前i时间的所有都给B会场 
             for(ri k=1;k<=i;++k){
                  pre[i][j]=max(pre[i][j],pre[k][j]+sum[k][i]);//从前k的时间选j个给A会场 后k~i的时间都给B会场 
                  if(j>=sum[k][i]) pre[i][j]=max(pre[i][j],pre[k][j-sum[k][i]]);
            //从前k的时间选j-sum[k][i]个给A会场 后面再选sum[k][i]个给A会场 就凑齐了j个给A会场 
            }
        }
        //反着再求一次aft 表示从i~t A会场选j个,B会场选的个数 
        for(ri i=t;i>=1;--i)
         for(ri j=0;j<=sum[i][t];++j){
             if(j==0) { aft[i][j]=sum[i][t]; continue; }
             for(ri k=t;k>=i;--k){//从后往前枚举断点 类区间dp的更新 
                  aft[i][j]=max(aft[i][j],aft[k][j]+sum[i][k]);
                  if(j>=sum[i][k]) aft[i][j]=max(aft[i][j],aft[k][j-sum[i][k]]);
            }
        }
          
        int ans=0;
        for(ri i=1;i<=n;++i) ans=max(ans,min(i,pre[t][i]));
        printf("%d
    ",ans);
        //ans[l][r]表示强制选l到r这段区间的最大数 
        for(ri k=1;k<=n;++k)
         for(ri i=u[k];i>=1;--i)//将左端点向左延伸 
          for(ri j=v[k];j<=t;++j){//右端点向右延伸 
              for(ri l=0;l<=sum[1][i];++l){//让A在左边选l个 
               for(ri r=0;r<=sum[j][t];++r){//让A在右边选r个 
                       //中间的全部分给A 
                    anss[k]=max(anss[k],min( sum[i][j]+l+r , pre[i][l]+aft[j][r] ));
                    if(aft[j][r]<r) break;//r在增加时 aft[j][r]是单调递减的 
                    //这个剪枝的意思是:右边选的太少了 B会场相应的也会很少 答案就不会再被更新了 
                }
                if(pre[i][l]<l) break;//同上 
            }
            if(sum[i][j]>sum[1][i]+sum[j][t]) break;//A选得太多了 会导致A、B不平衡 答案也无法被更新 相当于一个剪枝 
            if(anss[k]>sum[1][i]+sum[j][t]) break;//左右全部都分给B都更新不了答案 再枚举也没有意义了 
        }
           
        for(ri i=1;i<=n;++i) printf("%d
    ",anss[i]);
    }
    /*
    10
    
    15 6
    
    13 12
    
    20 17
    
    1 18
    
    5 14
    
    18 9
    
    15 25
    
    21 16
    
    16 9
    
    19 14
    
    
    
    5 
    8 2 
    1 5 
    5 3 
    3 2 
    5 3 
    */
    View Code
  • 相关阅读:
    CTF-域渗透--SSH服务
    CTF-域渗透--SMB信息泄露
    CTF-域渗透--FTP服务后门利用
    Codeforces Round #628 (Div. 2)
    模板
    模板
    Educational Codeforces Round 6
    Educational Codeforces Round 5
    模板
    Codeforces Round #627 (Div. 3)
  • 原文地址:https://www.cnblogs.com/mowanying/p/11514836.html
Copyright © 2020-2023  润新知