题解「Luogu5665 划分」

转载注明来源:https://www.cnblogs.com/syc233/p/13663639.html


丧心病狂卡时空题


题意

给你一个长为 (n) 的数列 ({a_n}) ,需要找到若干个分界点 (1 leq k_1 <k_2 <k_3<cdots<k_p < n) ,满足:

[sum_{i=1}^{k_1} a_i leq sum_{i=k_1+1}^{k_2}a_i leq cdotsleq sum_{i=k_p+1}^na_i ]

同时最小化

[(sum_{i=1}^{k_1} a_i)^2 + (sum_{i=k_1+1}^{k_2}a_i)^2 + cdots + (sum_{i=k_p+1}^na_i)^2 ]


题解

容易想到一个 (O(n^2)) 的DP:令 (f_i) 表示划分 (1 sim i) 的最小答案, (g_i) 表示答案取到 (f_i) 时上一段的末尾, (s_i)({a_n}) 的前缀和。则有:

[f_i=min_{0leq j <i}{f_j+(s_i-s_j)^2|(s_j-s_{g_j})leq(s_i-s_j)} ]

更新 (f_i) 时顺带更新下 (g_i)

这样能拿到 (64pt)

注意到这样一个性质:最后一段的和越小,答案越优。

那么有:

[egin{aligned} g_i&=max pos s.t. (s_i-s_{pos})geq(s_{pos}-s_{g_{pos}})\ &=max pos s.t. s_igeq(2 cdot s_{pos}-s_{g_{pos}})\ end{aligned} ]

({ m{val}}(i)=2cdot s_i-s_{g_i}) ,则:

[g_i=max pos s.t. s_igeq{ m{val}}(pos) ]

因为 (s_i) 是递增的,所以若存在 (a,b) ,满足 (a<b<i,{ m{val}}(a)>{ m{val}}(b)) ,那么 (a) 能够转移时, (b) 一定能够转移,那 (a) 必定不会成为最优解。于是可以用单调队列来求出 (g_i)

由于这题时空都卡得比较死,所以最后用 (g_i) 来求出答案。


( ext{Code}:)

#include <cstring>
#include <cstdio>
#define maxn 40000005
#define maxm 100005
#define Rint register int
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;

template <typename T>
inline void read(T &x)
{
	x=0;T f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	x*=f;
}

void write(__int128 x)
{
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

int n,type,a[maxn],p[maxm],L[maxm],R[maxm];
lxl sum[maxn];
int q[maxn],l,r;

inline void make()
{
	int m;lxl x,y,z,bpp,bp,bn;
	lxl mod=1<<30;
	read(x),read(y),read(z),read(bpp),read(bp),read(m);
	for(int i=1;i<=m;++i) read(p[i]),read(L[i]),read(R[i]);
	for(int j=1;j<=m;++j)
	{
		for(int i=p[j-1]+1;i<=p[j];++i)
		{
			if(i==1) bn=bpp;
			else if(i==2) bn=bp;
			else bn=(x*bp+y*bpp+z)%mod;
			a[i]=bn%(R[j]-L[j]+1)+L[j];
			if(i>=3) bpp=bp,bp=bn;
		}
	}
}

int pre[maxn];

int main()
{
	// freopen("P5665.in","r",stdin);
	read(n),read(type);
	if(!type) for(int i=1;i<=n;++i) read(a[i]);
	else make();
	for(int i=1;i<=n;++i)
		sum[i]=sum[i-1]+a[i];
	l=1,r=0;
	for(int i=1;i<=n;++i)
	{
		while(l<=r&&2*sum[q[l]]-sum[pre[q[l]]]<=sum[i]) ++l;
		pre[i]=q[l-1];
		while(l<=r&&2*sum[q[r]]-sum[pre[q[r]]]>=2*sum[i]-sum[pre[i]]) --r;
		q[++r]=i;
	}
	__int128 ans=0;
	int now=n;
	while(now)
	{
		ans+=((__int128)sum[now]-sum[pre[now]])*(sum[now]-sum[pre[now]]);
		now=pre[now];
	}
	write(ans);
	return 0;
}