【CSP2019】划分
单调队列优化DP。
题目大意:
给定一个数列,要求将其划分为若干段。令第 $i$ 段的和为 $b_i$,划分成了 $k$ 段。
要求满足 $\forall i\in[2,n]$,$b_i\geq b_{i-1}$。
在此基础上最小化 $\sum\limits_{i=1}^k b^2_i$.
求最小值。
题解:
这玩意我不太会证啊
大概就是令最后一段尽可能小,令前一段尽可能小,依此类推。这样能使答案最优。感性理解吧
等我看懂了证明过程再来补。flag
这个东西的话,就是对每个位置,记录其最小的分段的位置。
这个可以用单调队列优化。
考虑位置 $i,j,k$ 满足 $i
考虑在队尾插入一个位置 $p$,队尾位置为 $i$,如果满足任意 $k>p$ 使 $[i,k]$ 合法的都能使 $[p,k]$ 合法,那么队尾元素也没有用了。弹出队尾即可。
这样保证每次决策时,队首都是最优的且后面的划分方案都不合法。
时间复杂度 $O(n)$。
这里只需记录每段的划分位置,最后计算答案。计算答案时需要用到高精度,用两个long long
存即可。__int128
在很多地方不能用,当做练习高精度好了。
注意前缀和一些奇奇怪怪的数组越界问题(
Code:
#include<iostream>
#include<cstdio>
using namespace std;
typedef unsigned long long LL;
const LL inf=1e18;
const int base=1e9;
const int N=4e7+3;
int n,type;
LL pre[N];
int sta[N],head,tail,lst[N];
inline LL sum(int l,int r){return pre[r]-pre[l-!!l];}
struct int128{
LL a,b;
inline int128 operator+(const int128&r)const{
return(int128){a+r.a+(b+r.b>=inf),b+r.b-(b+r.b>=inf)*inf};
}
}ans;
inline int128 sqr(LL x){
int a=x/base,b=x%base;
LL f=(LL)a*a+(LL)a*b*2/base,g=(LL)a*b*2%base*base+(LL)b*b;
return(int128){f+g/inf,g%inf};
}
void get(){
int x,y,z,m,ed=0;
cin>>x>>y>>z>>pre[1]>>pre[2]>>m;
for(register int i=3;i<=n;++i)
pre[i]=(x*pre[i-1]+y*pre[i-2]+z)&1073741823;
while(m--){
int p,l,r;
cin>>p>>l>>r;
for(register int i=ed+1;i<=p;++i)
pre[i]=pre[i-1]+(pre[i]%(r-l+1)+l);
ed=p;
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>type;
if(!type)
for(int i=1;i<=n;++i)cin>>pre[i],pre[i]+=pre[i-1];
else get();
sta[head=tail=1]=0;
for(register int i=1;i<=n;++i){
while(head<tail&&sum(lst[sta[head+1]],sta[head+1])<=sum(sta[head+1]+1,i))
++head;
lst[i]=sta[head]+1;
const LL s=sum(lst[i],i);
while(head<=tail&&sum(lst[sta[tail]],sta[tail])>=s+sum(sta[tail]+1,i))
--tail;
sta[++tail]=i;
}
for(int p=n;p;p=lst[p]-1)ans=ans+sqr(sum(lst[p],p));
if(ans.a==0)printf("%llu\n",ans.b);else
printf("%llu%018llu\n",ans.a,ans.b);
return 0;
}