目录
显示
题目链接
https://www.luogu.com.cn/problem/P5665
题解
36 分的做法比较显然:我们设 \(s_i\) 为前缀和,\(f(i,j)\) 代表前 \(i\) 个数,上一段的末尾为 \(j\) 时的最小值。
转移方程为:
$$f(i,j)=\min f(j,k)+(s_i-s_j)^2$$
正解是怎样的呢?我们首先有两个引理:
- 所有合法的划分方案中,最优解的段数是最少的。
- 在 \(1\) 的前提下,设最优解分为 \(k\) 段,最优解的每段末尾的下标分别为 \(s_1,s_2,\ldots ,s_k\),一个合法解的每段末尾的下标分别为 \(t_1,t_2,\ldots ,t_k\),则 \(\forall i \in [1,k]\),都有 \(s_i \geq t_i\)。
证明可以看 myy 的题解(感性理解似乎也不是很难)。
这样我们就可以用单调队列在 \(O(n)\) 的时间内求出所有断点了。
如果不考虑高精度的部分,我们已经拿到了 88 分。
幸运的是,本题只有答案部分要用到高精度:\(s_i\) 足够用 64 位整数存储,从而最终答案在 128 位整数的存储范围之内。
NOI Linux 是 32 位机,__int128
并不能用。考场上一种简单的高精度实现是类似压位高精度的做法——用两个 long long
组合起到 128 位整数的作用。
(当然各大 OJ 基本都是 64 位机,就可以为所欲为了)
另外,本题对内存限制较紧,因此 int
足够存下的数据就别开 long long
了。
#include <iostream> #define MAXN 40000005 #define MOD 1073741824 using namespace std; long long s[MAXN]; int n,op,a[MAXN],b[MAXN],p[MAXN],q[MAXN]; void geninput() { if(!op) { for(int i=1;i<=n;i++) cin>>a[i]; } else { long long x,y,z,m; cin>>x>>y>>z>>b[1]>>b[2]>>m; for(int i=3;i<=n;i++) b[i]=(x*b[i-1]+y*b[i-2]+z)%MOD; int lp=0; while(m--) { int p,l,r; cin>>p>>l>>r; for(int i=lp+1;i<=p;i++) a[i]=(b[i]%(r-l+1))+l; lp=p; } } } long long f(int x) { return 2*s[x]-s[p[x]]; } void print(__int128 x) { if(x<10)cout<<(int)x; else { print(x/10); cout<<int(x%10); } } int main() { ios::sync_with_stdio(false); cin>>n>>op; geninput(); for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i]; int l=0,r=0; for(int i=1;i<=n;i++) { while(l<r&&f(q[l+1])<=s[i]) l++; p[i]=q[l]; while(l<=r&&f(q[r])>=f(i)) r--; q[++r]=i; } __int128 ans=0; for(int i=n;i;i=p[i]) ans+=(__int128)(s[i]-s[p[i]])*(s[i]-s[p[i]]); print(ans); return 0; }