【题解】LuoGu5665:划分

原题传送门
考场上我先是一个暴力dp
d p i , j dp_{i,j} dpi,j表示前 i i i个数最后一组有 j j j个的答案
枚举 i , j , k i,j,k i,j,k就行了
发现有冗余转移,可以把 k k k省掉
达到 O ( n 2 ) O(n^2) O(n2),此方法就已经到头了,因为状态就是 O ( n 2 ) O(n^2) O(n2)

我的一个基友在考场上5min秒掉 O ( n 2 ) O(n^2) O(n2),而且状态是一维的,但他当时无法证明正确性
可以发现我的上一个方法中 d p i , j dp_{i,j} dpi,j一定比 d p i , j + 1 dp_{i,j+1} dpi,j+1
换句话说,以确定某个数结尾后,合理的情况下,最后一组的数越少(最后一组总和越小)越好
证明: a 2 + b 2 < ( a + b ) 2 a^2+b^2<(a+b)^2 a2+b2<(a+b)2
所以对于每个 i i i,只需要记录最优解下,这最后一组有几个(上一组结尾下标)与dp值就行了
p r e i pre_i prei表示最优解下以 i i i结尾上一组结尾下标
d p i dp_i dpi表示dp数组
s u m i − s u m j > = s u m j − s u m p r e j 且 d p j + ( s u m i − s u m j ) 2 < d p i sum_i-sum_j>=sum_j-sum_{pre_j}\text{且}dp_j+(sum_i-sum_j)^2sumisumj>=sumjsumprejdpj+(sumisumj)2<dpi
d p i = 那一坨 , p r e i = j dp_i=\text{那一坨},pre_i=j dpi=那一坨,prei=j
这也是 O ( n 2 ) O(n^2) O(n2)

那么既然状态优化成了 O ( n ) O(n) O(n),接下来就可以考虑优化时间了
对于每个 i i i p r e i pre_i prei一定是越大越好,所以其实本题根本不用dp,因为最优解的计算是确定的,就是找到最大的 j j j,使得 s u m i − s u m j > = s u m j − s u m p r e j sum_i-sum_j>=sum_j-sum_{pre_j} sumisumj>=sumjsumprej
移项, s u m i > = 2 s u m j − s u m p r e j sum_i>=2sum_j-sum_{pre_j} sumi>=2sumjsumprej,左边是递增的,所以单调队列维护即可求得答案

以上解决了88pts,剩下12pts,显然需要高精,然而,我用了__int128

Code:

#include 
#define maxn 40000010
#define maxm 100010
#define LL long long
using namespace std;
const LL qy = 1 << 30;
int n, type, pre[maxn], h, t, q[maxn];
LL sum[maxn], b[maxn], p[maxm], l[maxm], r[maxm], m;
__int128 ans;

inline LL read(){
	LL s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

void write(__int128 x){
	if (!x) return;
	if (x < 0) putchar('-'), x = -x;
	write(x / 10), putchar(x % 10 + '0');
}

LL calc(int x){ return (sum[x] << 1) - sum[pre[x]]; }

int main(){
	n = read(), type = read();
	if (type == 1){
		LL x = read(), y = read(), z = read();
		b[1] = read(), b[2] = read();
		LL m = read();
		for (int i = 1; i <= m; ++i) p[i] = read(), l[i] = read(), r[i] = read();
		for (int i = 3; i <= n; ++i) b[i] = (x * b[i - 1] + y * b[i - 2] + z) % qy;
		for (int i = 1; i <= m; ++i)
			for (int j = p[i - 1] + 1; j <= p[i]; ++j) sum[j] = sum[j - 1] + b[j] % (r[i] - l[i] + 1) + l[i]; 
	} else
	for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + read();
	q[++t] = 1;
	for (int i = 2; i <= n; ++i){
		while (h < t && sum[i] >= calc(q[h + 1])) ++h;
		pre[i] = q[h];
		while (h < t && calc(i) <= calc(q[t])) --t;
		q[++t] = i;
	}
	while (n) ans += (__int128)(sum[n] - sum[pre[n]]) * (sum[n] - sum[pre[n]]), n = pre[n];
	write(ans);
	return 0;
}

你可能感兴趣的:(题解,noip,DP)