【題解】LuoGu5665:劃分

原題傳送門
考場上我先是一個暴力dp
dpi,jdp_{i,j}表示前ii個數最後一組有jj個的答案
枚舉i,j,ki,j,k就行了
發現有冗餘轉移,可以把kk省掉
達到O(n2)O(n^2),此方法就已經到頭了,因爲狀態就是O(n2)O(n^2)

我的一個基友在考場上5min秒掉O(n2)O(n^2),而且狀態是一維的,但他當時無法證明正確性
可以發現我的上一個方法中dpi,jdp_{i,j}一定比dpi,j+1dp_{i,j+1}
換句話說,以確定某個數結尾後,合理的情況下,最後一組的數越少(最後一組總和越小)越好
證明:a2+b2<(a+b)2a^2+b^2<(a+b)^2
所以對於每個ii,只需要記錄最優解下,這最後一組有幾個(上一組結尾下標)與dp值就行了
preipre_i表示最優解下以ii結尾上一組結尾下標
dpidp_i表示dp數組
sumisumj>=sumjsumprejdpj+(sumisumj)2<dpisum_i-sum_j>=sum_j-sum_{pre_j}\text{且}dp_j+(sum_i-sum_j)^2<dp_i
dpi=那一坨,prei=jdp_i=\text{那一坨},pre_i=j
這也是O(n2)O(n^2)

那麼既然狀態優化成了O(n)O(n),接下來就可以考慮優化時間了
對於每個iipreipre_i一定是越大越好,所以其實本題根本不用dp,因爲最優解的計算是確定的,就是找到最大的jj,使得sumisumj>=sumjsumprejsum_i-sum_j>=sum_j-sum_{pre_j}
移項,sumi>=2sumjsumprejsum_i>=2sum_j-sum_{pre_j},左邊是遞增的,所以單調隊列維護即可求得答案

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

Code:

#include <bits/stdc++.h>
#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章