题解 P2688 【大海战】

· · 题解

第一个点没有询问,只需要判断初始棋盘能否放下就行了。——直接把所有ci*ti加起来,判是否小于等于n即可.

第2到第8个点,只有一次询问。这个询问把棋盘分成了两段,我们考虑其中较短的那部分,就变成了一个背包问题——要在这部分中放置尽量多的物品,空间浪费得越少越好。(至于剩下的那部分,直接用物品总体积减去前面能够放入的物品体积,与剩余的空间进行比较即可。)每个战舰的价值即为它的长度,直接多重背包即可。第2、3个点直接是01背包,而后面的点由于c*n较大,朴素的背包会T,需要用单调队列优化得到O(cn)的算法.

第9到14个点,只有一种物品。我们二分答案,那么询问序列把棋盘分成了多个部分。假设某一段的长度为L,那么就可以放下L/size个物品。于是我们扫一遍就可以统计最多放置的物品数量,然后再根据要求二分下去即可。

第15到20个点,有两种物品。在二分的基础上(设分为了mid段),我们用f[i][j]表示考虑到前i段,恰好放置j种1号物品的前提下,可以放置的最多2号物品的数量。设第i段的长度为Li,转移方程为f[i][j]=max(f[i-1][j-k]+(Li-k*size1)/size2),其中0<=k<=j且k*size1<=Li。那么f[mid][num1]就是当前状态可以放置的最多2号物品的数量,与要求进行比较,再二分即可。看上去这个方程是O(n^3)的,但实际上我们有k<=Li,因此对于确定的j,考虑所有的i,总共的决策数量不会超过sigma(Li),即不会超过O(n)个。所以DP的复杂度为O(n^2),整个算法的时间复杂度为O(n^2logq),但远远达不到这个界,稍微优化一下就可以过了。

代码(c++)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 500010
int n,c,q,siz[maxn],num[maxn],od[maxn],que[maxn];
int f[maxn],sta[maxn],sta2[maxn],len[maxn],dp[2][4010];
bool cmp(const int &p,const int &q)
{
    return que[p]<que[q];
}
int main()
{
    int t; scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&c,&q);
        memset(f,0,sizeof(f));
        long long totsum=0;
        for(int i=1; i<=c; i++)
        {
            scanf("%d%d",&siz[i],&num[i]);
            totsum+=1ll*siz[i]*num[i];
        }
        for(int i=1; i<=q; i++)
        {
            od[i]=i;
            scanf("%d",&que[i]);
        }
        if(totsum>n) {printf("0\n"); continue;}
        if(q==0)
        {
            printf("-1\n");
        }
        else if(q==1)
        {
            int now=min(que[1]-1,n-que[1]);
            for(int i=1; i<=c; i++)
            {
                for(int j=0; j<siz[i]; j++)                 {
                    int ta=1,fr=1;
                    sta[1]=f[j]; sta2[1]=0;                    for(int k=1,e=siz[i]; e+j<=now; k++,e+=siz[i])                    {
                        int curf=f[e+j]-e;
                        while(ta<=fr && sta[fr]<=curf) fr--;
                        sta[++fr]=curf; sta2[fr]=k;
                        if(sta2[fr]-sta2[ta]>num[i]) ta++;
                        f[e+j]=sta[ta]+e;
                    }
                }
            }
            if(f[now]+max(que[1]-1,n-que[1])>=totsum)
                printf("-1\n");
            else printf("1\n");
        }
        else if(c==1)
        {
            sort(od+1,od+1+q,cmp);
            int l=0,r=q+1,mid=(l+r)>>1;
            while(l<r)
            {
                int ans=0,lst=0;
                for(int i=1; i<=q; i++)
                    if(od[i]<=mid)
                    {
                        ans+=(que[od[i]]-lst-1)/siz[1]*siz[1];
                        lst=que[od[i]];
                    }
                ans+=(n-lst)/siz[1]*siz[1];
                if(ans>=totsum) l=mid+1;
                else r=mid;
                mid=(l+r)>>1;
            }
            if(mid!=q+1) printf("%d\n",mid);
            else printf("-1\n");
        }
        else if(c==2)
        {
            sort(od+1,od+1+q,cmp);
            int l=0,r=q+1,mid=(l+r)>>1;
            while(l<r)
            {
                int tot=0,lst=0;
                for(int i=1; i<=q; i++)
                    if(od[i]<=mid)
                    {
                        len[++tot]=que[od[i]]-lst-1;
                        lst=que[od[i]];
                    }
                len[++tot]=n-lst;
                memset(dp,-0x3f,sizeof(dp));
                                dp[0][0]=0;
                for(int i=1; i<=tot; i++)
                    for(int j=0; j<=num[1]; j++)
                    {
                        for(int k=0,e=0; k<=j && e<=len[i]; k++,e+=siz[1]) //how many 1st items
                            dp[i&1][j]=max(dp[i&1][j],dp[(i&1)^1][j-k]+(len[i]-e)/siz[2]);
                    }
                                if(dp[tot&1][num[1]]>=num[2]) l=mid+1;
                else r=mid;
                mid=(l+r)>>1;
            }
            if(mid!=q+1) printf("%d\n",mid);
            else printf("-1\n");
        }
    }
    return 0;
}