[CSP-S2019] 划分 题解

Statement

划分 - 洛谷

Solve

8pts ,暴力枚举分界点 \(O(2^nn)\)

36pts,\(O(n^3)\) \(\text{DP}\)

我们设 \(f[i][j]\) 表示前 \(i\) 个数,最后一段开始节点 \(j\) 的最小代价

设 \(sum[i]=\sum_{j=1}^ia[j]\)

那么,\(f[i][j]=min\{f[j-1][k]+(sum[i]-sum[j-1])^2\}\)

初态:\(f[i][1]=s[i]^2\) 答案:\(\sum_{i=1}^n f[n][i]\)

64pts, \(O(n^2)\) 贪心

首先,\((\sum_{i=1}^ka_i)^2\geq\sum_{i=1}^ka_i^2\) ,这个引导我们多分段

然后,我们知道一个显然的结论:最后一段尽量小的答案更优

简单证明:

[CSP-S2019] 划分 题解

假设 \(sum1+sum2<sum3,sum2<sum1\) ,考虑红色段应该分在哪边

(这个假设实际上是保证了 可以分在左边+需要分)

左边:\((sum1+sum2)^2+sum3^2\)

右边:\((sum3+sum2)^2+sum1^2\)

右 \(-\) 左:\(2\times sum2(sum3-sum1)\)

因为我们假设 \(sum1+sum2<sum3\) ,所以 \(sum3-sum1>0\)

也就是说,分在右边比分在左边的代价高,所以,最后一段应该尽量小。

——

我们把这个贪心结论融合入刚刚的 \(dp\)

此时,上一段的起点 \(k\) 确定,直接更新即可

这里,设 \(f[i]\) 表示前 \(i\) 个数字分段的最小代价,\(pos[i]\) 表示以 \(i\) 结尾的段的最大开头

for(int i=2;i<=n;++i)
    for(int j=1;j<i;++j)
        if(f[i]>f[j]+(sum[i]-sum[j])*(sum[i]-sum[j])&&sum[i]-sum[j]>=sum[j]-sum[pos[j]])//值得更新且可以更新
            f[i]=f[j]+(sum[i]-sum[j])*(sum[i]-sum[j]),pos[i]=j;

因为我们是顺序枚举 \(j\) ,所以最后 \(pos[i]\) 尽量大,段值尽量小

100pts

参考:题解 P5665 - Soulist

我们可以在挖掘一下之前的贪心

显然,\(f[j]\) 能更新 \(f[i]\) 的一个条件是 \(sum[i]-sum[j]\geq sum[j]-sum[pos[j]]\)

移项:\(sum[i]\geq 2\times sum[j]-sum[pos[j]]\)

设 \(calc(i)=2\times sum[i]-sum[pos[i]]\)

那么我们要找的其实就是满足上述条件的最大的一个 \(j\)

因为 \(sum\) 单调上升,发现决策集合单调增长

考虑单调队列维护一个递增下标序列(决策集合)

  • 只要 \(calc[q[l+1]]<=sum[i]\) ,那么 \(l++\) ,因为我们找的是最大的 \(j\) 来更新 \(i\)

  • 用 \(q[l]\) 更新 \(i\)

  • 只要 \(calc[q[r]]>=calc[i]\),那么 \(r--\) ,因为 \(i\) 相对于 \(q[r]\) 显然是一个更好的决策,不仅比他靠后,而且值更小,更容易满足条件去更新其他的

  • \(i\) 入队

int l=1,r=1;
//q[1]=0;
for(int i=1;i<=n;++i){
    while(l<r&&calc(q[l+1])<=sum[i])l++;//因为我们要用 q[l] 更新,所以比较下一位
    f[i]=f[q[l]]+(sum[i]-sum[q[l]])*(sum[i]-sum[q[l]]);
    while(l<=r&&calc(q[r])>=calc(i))r--;
    q[++r]=i;
}

——

假装这样就可以了

但这样即使开 \(\_\_int128\) 也只有 \(88pts\)

我们需要高精度。

考虑到如果不压位的话会爆空间 ,所以我们还需要压位高精。

虽然这个复杂度是 \(O(n)\) 的,但是高精度还有一点常数,\(n\) 卡得太死啦,我们还要卡常

Code

#include<bits/stdc++.h>
#define max(x,y) x>y?x:y
using namespace std;
typedef unsigned long long ull;
const int mod = (1<<30)-1,base = 1e9;
const int N = 4e7+3, M = 1e5+3;

#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
int read(){
	int val=0;char c=gc();
	while(!isdigit(c))c=gc();
	while(isdigit(c))val=val*10+(c^48),c=gc();
	return val;
}

struct bigint{
	int len;
	ull num[4];
	bigint operator+(const bigint&rhs)const{
		bigint res;ull p=0;
		memset(res.num,0,sizeof(res.num));
		res.len=max(len,rhs.len);
		for(int i=0;i<res.len;++i){
			res.num[i]=num[i]+rhs.num[i]+p;
			p=res.num[i]/base;
			res.num[i]-=p*base;
		}
		if(p)res.num[res.len++]=p;
		return res;
	}
}ans;
int pos[N],b[N],q[N],p[M];
int n,type,z,m,l,r;
ull s[N],x,y;

ull calc(int x){
    return (s[x]<<1)-s[pos[x]];
}

int main(){
	n=read(),type=read();
	if(type){
        x=read();y=read();z=read();b[1]=read();b[2]=read();m=read();
        for(int i=3;i<=n;i++)b[i]=((x*b[i-1]&mod)+(y*b[i-2]&mod)+ z)&mod;
        for(int j=1;j<=m;++j){
        	p[j]=read(),l=read(),r=read();
        	for(int i=p[j-1]+1,a;i<=p[j];s[i]=s[i-1]+a,++i)
                a=b[i]%(r-l+1)+l;
        }
	}else for(int i=1,a;i<=n;s[i]=s[i-1]+a,++i)a=read();
	int l=1,r=1;
	for(int i=1;i<=n;++i){
		while(l<r&&calc(q[l+1])<=s[i])l++;//
		pos[i]=q[l];
		while(l<=r&&calc(q[r])>=calc(i))r--;//
		q[++r]=i;
	}
	ans.len=0;
	for(int i=n;i;i=pos[i]){
		bigint tmp,res;ull val=s[i]-s[pos[i]];tmp.len=0;
		while(val)tmp.num[tmp.len++]=val%base,val/=base;
		memset(res.num,0,sizeof(res.num));
		res.len=(tmp.len<<1)-1;ull p=0;
		for(int j=0;j<tmp.len;++j)
			for(int k=0;k<tmp.len;++k)
				res.num[j+k]+=tmp.num[j]*tmp.num[k];
		for(int j=0;j<res.len;++j){
			res.num[j]+=p;
			p=res.num[j]/base;
			res.num[j]-=base*p;
		}
		while(p)res.num[res.len++]=p%base,p/=base;
		ans=ans+res;
	}
	printf("%llu",ans.num[ans.len-1]);
	for(int i=ans.len-2;~i;--i)
		printf("%09llu",ans.num[i]);
	return 0;
}
上一篇:CSP 2020 入门组第一轮试题


下一篇:P5657 [CSP-S2019] 格雷码