划分
题解
本题很明显的一道dp题。
我们可以先写出一个朴素的dp,暴力就不分析了,表示将j+1-i的划为一段的最小值。很明显,这是一个的算法,只能得到32分。我们需要考虑一下dp的优化,一眼单调队列(笔者也只想得到这个)。我们将划分点定下来,先将所有在它前面的塞入队列,再更新再划分点后面的点,就可以做到,有64分了。
之后是一个十分重要的结论:
,这其实通过单调队列分析得到。
接下来是证明,请别怪笔者能力有限,复制证明,笔者也没证出来。
结论
设最优解的倒数第段的开始位置为,那么对于所有的满足段和不递减条件的解,一定有下列条件之一:
- 该解不存在段。
- 该解从右到左数第段的开始位置至少为。
这里我们假设最优解的倒数最后一段(也就是第一段)的开始位置是1,所以上述条件蕴含没有任何解的段数比最优解要多。
换句话说,如果你把所有解的断点从大到小写下来,然后剩下的位置补0,那么最优解对应的序列在所有位置都是最大值。
同时容易注意到满足这个结论中条件的解一定唯一,因此最优解释良定义的。
根据结论推出的线性做法
设pipi表示以ii结尾的前缀,最后一段的位置的最大值,那么pipi一定是满足
的最大的。
这个非常显然,因为对应的是每个位置结尾的前缀最后一段的最小和,如果不存在使得上述条件满足,那么一定是最后一段位置的开头的位置的最大值。
通过记录前缀和,这个东西很好用单调队列线性维护。
最后答案就是按照不断往前走。
容易证明到满足不递减性,所以按照不断走得到的解一定是满足结论中条件的解。
结论的证明
对于每个解,我们可以从后往前将每一段的和写出来,然后补无限个零,得到一个对应的序列。
从结论我们容易推出,满足结论中条件的解对应的序列的任何位置的前缀和一定是所有解对应位置的前缀和中最大的。
现在,我们抛弃原序列,只考虑这个和构成的序列,假设满足结论中条件的解对应的序列是,我们现在找到另外一个解,它的序列是,且满足:
我们需要证明:
。
我们注意到对于一个单调不递增的序列,如果我们选出两个下标,使得,并将减一,加一,那么操作之后依然单调不递增,且么这个操作会使的平方和减少。
证明的思路是,证明可以通过一些合法的移动将变为,且任意时刻的所有位置前缀和仍然大于的对应位置的前缀和。这样就可以证明的平方和一定不会小于的平方和。
我们只要证明对于任意不同于的,可以找到一个合法的,保持前缀和性质的移动,因为一次移动之后可以使平方和变小,证明的剩下部分很好使用按照平方和的数学归纳法解决。
我们找到第一个的前缀和大于的前缀和的位置,因为的前缀和不会小于,如果不存在这样的位置,那么只可能和相同,这与和不同的假设矛盾。
假设这个位置为,我们找到第一个使得这个位置两个序列对应的前缀和相等,因为和的总和相等且我们补了零,容易发现这个位置一定能找到。
考虑到。如果你写下的值,在这个区间内这个值的和为0,且(因为是第一个的前缀和大于的前缀和的位置),那么一定有另一个位置,由于单调不递减,肯定有这个位置的,这意味着这个区间中的权值的跨度至少为2。
那么我们找到最小的的,最大的的,设容易发现这是一个合法的移动,且保持了前缀和性质。
于是任何一个解对应的序列都可以通过若干次移动得到满足结论中条件的解对应的解,这就证明了满足结论中条件的解的平方和是最小的,是最优解。
以上就是证明,所以,我们可以在记录下每个dp的最大值与其划分的位置,很容易就能达到,用单调队列维护就可以达到,这样就有了88分。之后就是最坑的的情况,需要高精进行处理,其实出题者也就想卡掉而已,顺便增加一下码量,好像有许多巨佬考场上都未去拿这12分。一个压位高精,不过要尽量压一下,否则会被卡常。好像有人用__int128卡过了。
源码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXM 100005
#define MAXN 40000005
using namespace std;
typedef long long LL;
const LL mo=(1<<30);
const LL W=1000000;
LL n,type,sum[MAXN];
LL nxt[MAXN],dp[MAXN];
LL q[MAXN],head,tail;
LL ans[5],len;
template<typename _T>
inline void read(_T &x)
{
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
inline void gjf(LL x)
{
LL s=x,b[5]={},lenb=0;
while(s) b[lenb++]=s%W,s/=W;
s=0;
for(int i=0;i<lenb||s;i++)
{
LL k=(i<lenb?b[i]:0)*x+s;
//printf("%lld %lld %lld\n",x,i,k);
s=k/W;ans[i]+=k%W;
if(i>=len) len++;
}
for(int i=0;i<len||s;i++)
{
LL k=ans[i]+s;
s=k/W;ans[i]=k%W;
if(i>=len) len++;
}
}
int main()
{
read(n);read(type);
LL x,y,z,m,b1,b2;
if(type==1)
{
read(x);read(y);read(z);
read(b1);read(b2);read(m);
}
LL p=0,l=0,r=0;
head=tail=1;q[tail]=0;
for(int i=1;i<=n;i++)
{
if(type==0)
{
LL x;read(x);
sum[i]=sum[i-1]+x;
}
else
{
LL a,b;
if(p<i)
read(p),read(l),read(r);
if(i>2)
{
b=(x*b2%mo+y*b1%mo+z)%mo;
b1=b2;b2=b;
}
if(i==1) b=b1;
if(i==2) b=b2;
a=b%(r-l+1)+l;
sum[i]=sum[i-1]+a;
}
while(head<tail&&sum[i]-sum[q[head+1]]>=dp[q[head+1]]) head++;
dp[i]=sum[i]-sum[q[head]];nxt[i]=q[head];
while(head<tail&&dp[q[tail]]-sum[i]+sum[q[tail]]>=dp[i]) tail--;
q[++tail]=i;
}
for(int i=n;i>0;i=nxt[i])
{
LL xs=sum[i]-sum[nxt[i]];
gjf(xs);
}
printf("%lld",ans[len-1]);
for(int i=len-2;~i;i--)
printf("%06lld",ans[i]);
return 0;
}