[Noip2019]划分
[Noip2019]划分
一.前言
没保存哭了……重写。呕这该死的高精度,dev-c++,lemon,int128,O2,给我人都整傻了。本文不带高精度喔。,题目链接
二.思路
首先我们可以找到一些奇奇怪怪的dp,这之中较为突出的是 \(f[i][j]=min\{f[k][j]+(sum[i]-sum[j])^2\}\).但是!我们先不管他好吧。
根据和平方大于平方和的基本不等式来看,我们追求划分的段尽量多。而题目又有一个划分的每一段的和不下降的条件,很容易能想到最后一段越小,能划的段就越多(因为所有总和一定,都是段和)。于是乎我们每次追求转移的时候\((sum[i]-sum[j])^2\)的值最小,然后划掉平方,也就是使得段和最小。
由于前缀和不降,所以 j 尽量靠近 i (要大要小很难办w)(设段和为 \(sig\) )希望在 \(sum[i]-sum[j]>=sig[j]\) 的前提之下 j 尽量大,移项使得 \(sum[i]>=sig[j]+sum[j]\) 于是维护一个单调上升的队列就行。
三.CODE
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define m_for(i,a,b) for(int i=a;i<=b;++i)
int read(){
char ch=getchar();
int res=0,f=1;
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
return res*f;
}
const int MAXN=4*1e7;
int n,type,a[MAXN],dd[MAXN],head,tail,pre[MAXN];
long long sum[MAXN],sig[MAXN];
long long x,y,z,b[4],m;
void pt(__int128 a){
static int st[55];
while(a){
st[++st[0]]=a%10;
a/=10;
}
while(st[0])printf("%d",st[st[0]--]);
}
int main(){
n=read();type=read();
if(!type)m_for(i,1,n)a[i]=read();
else{
x=read();
y=read();
z=read();
b[1]=read();
b[2]=read();
m=read();
for(int i=1,last=0,p,l,r;i<=m;++i)
{
p=read();
l=read();
r=read();
for(int j=last+1;j<=p;++j)
{
if(j>2)
{
b[3]=((long long)x*b[2]+(long long)y*b[1]+z)&((1 << 30) - 1);
b[1]=b[2];
b[2]=b[3];
a[j]=b[3]%(r-l+1)+l;
}
else a[j]=b[j]%(r-l+1)+l;
}
last=p;
}
}
m_for(i,1,n)sum[i]=sum[i-1]+1LL*a[i];
m_for(i,1,n){
while(head<tail&&sig[dd[head+1]]+sum[dd[head+1]]<=sum[i])++head;
sig[i]=sum[i]-sum[dd[head]];
pre[i]=dd[head];
while(head<tail&&sig[dd[tail]]+sum[dd[tail]]>=sig[i]+sum[i])--tail;
dd[++tail]=i;
}
__int128 ans=0LL;
int now=n;
while(now){
ans+=__int128(sig[now])*sig[now];
now=pre[now];
}
pt(ans);
return 0;
}
我无论是手写高精还是__int128 都用不起,只有 88 分,就酱。