[CSP-S2019]划分 题解

CSP-S2 2019 D2T2

考场上读完题感觉是DP就直接扔了开T3了,考完才发现部分分好像不难拿,枯了


题意分析

给出一个数列,要求将其分成几段,使每段的和非严格递增,求最小的每段的和的平方和。

思路分析

可以发现,分成的段越多,即合并越少,答案越小;因此我们希望每段的和都尽量小。这提供了一个贪心的思想来解题。

以上只是合情推理,大家看着开心就好,不过看起来似乎没什么问题

设$f_i$表示数列$[a_1,a_i]$最后一段的和最小时最后一个断点(若需要整段合并,则为0)。这个$f_i$需要在使分段满足题目要求的单调性的前提下,尽量地大,也就是使最后一段的和尽量小,从而使每段的和都尽量小。因此,对于每个$i$,我们可以找到最大的合法的$f_i$。每次都遍历一遍暴力查找,复杂度$O(n^2)$,预计可以得到64分的高分。

查找时用数列前缀和来求段和,设$sum_i$表示$i$位置的前缀和。

通过分析或者手玩样例可以发现,$f_i$一定是单调不降的,并且判定合法的条件$sum_i-sum_j\geq sum_j-sum_{f_j}$,可以变形为$sum_i\geq sum_j+(sum_j-sum_{f_j})$,根据题意,$sum_j-sum_{f_j}$和$sum_j$都具有单调性。显然,这可以用单调队列来优化,复杂度$O(n)$。

据说,为了防止被看出可以直接推出分段然后求解答案,出题人放弃了取模而使用了高精。然而我太懒,本文的代码直接用int128实现。当然,考场上是用不了int128的,还是要熟练高精才行。

注意要尽量优化空间,不需要存储的信息可以不用存储,否则会导致正解MLE的惨状。

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
#define INT __int128
using namespace std;
const int N=4e7+100,P=1073741824;
int n,type,head=1,tail=1,x,y,z,m,p,l,r;
int f[N],q[N];
ll b1,b2,b3,w;
ll sum[N];
INT ans;
void in1()
{
    scanf("%d%d%d%lld%lld%d",&x,&y,&z,&b1,&b2,&m);
    int now=1;ll w;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&p,&l,&r);
        while(now<=p)
        {
            if(now==1)
                w=(b1%(r-l+1))+l,sum[1]=w;
            else
                if(now==2)
                    w=(b2%(r-l+1))+l,sum[2]=sum[1]+w;
                else
                {
                    b3=(x*b2+y*b1+z)%P;
                    w=(b3%(r-l+1))+l,sum[now]=sum[now-1]+w;
                    b1=b2,b2=b3;
                }
            now++;
        }
    }
}//特殊输入
void write(INT x)
{
    if(x>9) 
        write(x/10);
    putchar(x%10+'0');
}//int128不能直接输出
int main()
{
    scanf("%d%d",&n,&type);
    if(type)
        in1();
    else
        for(int i=1;i<=n;i++)
            scanf("%lld",&w),sum[i]=sum[i-1]+w;
    for(int i=1;i<=n;i++)
    {
        while(head<tail && 2*sum[q[head+1]]-sum[f[q[head+1]]]<=sum[i])
            head++;//找到最大的f[i]
        f[i]=q[head];
        while(head<tail && 2*sum[q[tail]]-sum[f[q[tail]]]>=2*sum[i]-sum[q[head]])
            tail--;//维护单调性
        q[++tail]=i;
    }
    int now=n;
    while(now)
    {
        ans+=(INT)(sum[now]-sum[f[now]])*(sum[now]-sum[f[now]]);
        now=f[now];
    }//计算答案
    write(ans);
    return 0;
}
posted on 2019-11-21 22:03  TEoS  阅读(667)  评论(1编辑  收藏  举报