CodeForces-796E Exam Cheating 【DP】

题目链接

题意

在两个数轴上有一些整数(n<1000),用p个(p<1000)个长度小于等于k(k<=50)的区间去覆盖这些整数,求最多多少个整数能够被覆盖(相同的不重复计数)

分析

易想到这个四维DP的状态:
\[dp[n][p][r1][r2] \leftrightarrow 到n用了p个区间,上面覆盖的最后一个区间的长度为r1,下面的覆盖的最后一个区间的长度为r2\]

直接开这个数组,1000*1000*50*50会炸,通过下面的转移方程可以发现对n可以滚动数组优化。
至于转移方程就比较麻烦了,主要是考虑三种情况:

  1. 直接在前两个区间后面再加一个
  2. 新开一个区间
  3. 新开两个区间
    具体如何转移可以看下面的代码(可能写得比较繁琐,然而我觉得标程比我写得还繁琐),其中新开区间需要记录上一个位置的最大值,同样注意通过记录最大值来优化。

这样之后仍然无法再2s内跑完(\(O(npk^2)\) 2e9的复杂度)这里需要一个特别的优化:如果区间多到可以将n以内的每个数都能覆盖,则直接\(O(n)\)就可以算出答案。也就是说,如果\(p>\lceil \frac{n}{k} \rceil\)就不需要跑dp了,而剩下需要dp的情况最多为1000*1000*50就可以跑完

AC代码

//CodeForces-796E Exam Cheating
//AC 2017-4-12 18:41:19 by Carl Luo
//DP
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e3+100;

int n,p,k;
int dp[2][maxn][55][55];
int maks[2][2][maxn][55];
int tmaks[2][maxn];
int question[2][maxn];

int main()
{
    scanf("%d %d %d",&n,&p,&k);
    int cnt,x;
    int res=0;
    for(int i=0;i<2;++i)
    {
        scanf("%d",&cnt);
        while(cnt--)
        {
            scanf("%d",&x);
            question[i][x]=1;
        }
    }
    if(p>2*ceil(n/k))
    {
        for(int i=1;i<=n;++i)
            res+=(question[0][i]||question[1][i]);
        cout<<res<<endl;
        return 0;
    }
    int now=0;
    memset(dp,0xc0,sizeof dp);
    //dp[1][0][0][0]=0;
    memset(maks,0xc0,sizeof maks);
    memset(tmaks,0xc0,sizeof tmaks);
    maks[1][0][0][0]=maks[1][1][0][0]=tmaks[1][0]=0;
    for(int i=1;i<=n;++i)
    {
        //cout<<i<<"!!!!!!!!!!!!!!!!!"<<endl;
        for(int j=0;j<=p;++j)
        {
            tmaks[now][j]=-0x3f3f3f3f;
            for(int r=0;r<=k;++r)
            {
                maks[now][0][j][r]=maks[now][1][j][r]=-0x3f3f3f3f;
                for(int r1=0;r1<=k;++r1)
                    dp[now][j][r][r1]=-0x3f3f3f3f;
            }

        }
        for(int j=0;j<=p;++j)
        {
            //cout<<j<<endl;
            for(int r1=0;r1<=k;++r1)
            {
                for(int r2=0;r2<=k;++r2)
                {
                    int &cur=dp[now][j][r1][r2];
                    if(r1>1&&r2>1&&j>=2)
                        cur=max(cur,dp[!now][j][r1-1][r2-1]+(question[0][i]||question[1][i]));
                    if((r1==0||r1==1)&&j>=r1&&!(r2==0||r2==1))
                        cur=max(cur,maks[!now][0][j-r1][r2-1]+(question[1][i]||(question[0][i]&&r1)));
                    if((r2==0||r2==1)&&j>=r2&&!(r1==0||r1==1))
                        cur=max(cur,maks[!now][1][j-r2][r1-1]+((question[1][i]&&r2)||question[0][i]));
                    if((r1==0||r1==1)&&(r2==0||r2==1)&&j>=r1+r2)
                        cur=max(cur,tmaks[!now][j-r1-r2]+((question[0][i]&&r1)||(question[1][i]&&r2)));
                    //cout<<cur<<" ";
                    tmaks[now][j]=max(tmaks[now][j],cur);
                    maks[now][0][j][r2]=max(maks[now][0][j][r2],cur);
                    maks[now][1][j][r1]=max(maks[now][1][j][r1],cur);
                    res=max(res,cur);
                }
                //cout<<endl;
            }
            //cout<<"---------------------"<<endl;
        }
        now=!now;
    }
    printf("%d\n",res);
 }

转载于:https://www.cnblogs.com/DrCarlluo/p/6700799.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值