• [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_jgeq sum_j-sum_{f_j}$,可以变形为$sum_igeq 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;
    }
  • 相关阅读:
    每日一题
    每日一题
    mysql 约束
    七种基础排序算法代码总汇
    netty之bytebuf粘包、分包
    Java nio 简易练手版 模拟用户群聊
    Java网络编程之终端聊天菜鸟版
    双重检测优化与加锁操作redis
    centos部署smb服务
    从事 iOS 研发6年的面经——送给准备跳槽的你!
  • 原文地址:https://www.cnblogs.com/TEoS/p/11908728.html
Copyright © 2020-2023  润新知