P5665 [CSP-S2019] 划分

[CSP-S2019] 划分

题目描述

2048 年,第三十届 CSP 认证的考场上,作为选手的小明打开了第一题。这个题的样例有 n n n 组数据,数据从 1 ∼ n 1 \sim n 1n 编号, i i i 号数据的规模为 a i a_i ai

小明对该题设计出了一个暴力程序,对于一组规模为 u u u 的数据,该程序的运行时间 u 2 u^2 u2。然而这个程序运行完一组规模为 u u u 的数据之后,它将在任何一组规模小于 u u u 的数据上运行错误。样例中的 a i a_i ai 不一定递增,但小明又想在不修改程序的情况下正确运行样例,于是小明决定使用一种非常原始的解决方案:将所有数据划分成若干个数据段,段内数据编号连续,接着将同一段内的数据合并成新数据,其规模等于段内原数据的规模之和,小明将让新数据的规模能够递增。

也就是说,小明需要找到一些分界点 1 ≤ k 1 < k 2 < ⋯ < k p < n 1 \leq k_1 \lt k_2 \lt \cdots \lt k_p \lt n 1k1<k2<<kp<n,使得

∑ i = 1 k 1 a i ≤ ∑ i = k 1 + 1 k 2 a i ≤ ⋯ ≤ ∑ i = k p + 1 n a i \sum_{i=1}^{k_1} a_i \leq \sum_{i=k_1+1}^{k_2} a_i \leq \cdots \leq \sum_{i=k_p+1}^{n} a_i i=1k1aii=k1+1k2aii=kp+1nai

注意 p p p 可以为 0 0 0 且此时 k 0 = 0 k_0 = 0 k0=0,也就是小明可以将所有数据合并在一起运行。

小明希望他的程序在正确运行样例情况下,运行时间也能尽量小,也就是最小化

( ∑ i = 1 k 1 a i ) 2 + ( ∑ i = k 1 + 1 k 2 a i ) 2 + ⋯ + ( ∑ i = k p + 1 n a i ) 2 (\sum_{i=1}^{k_1} a_i)^2 + (\sum_{i=k_1+1}^{k_2} a_i)^2 + \cdots + (\sum_{i=k_p+1}^{n} a_i)^2 (i=1k1ai)2+(i=k1+1k2ai)2++(i=kp+1nai)2

小明觉得这个问题非常有趣,并向你请教:给定 n n n a i a_i ai,请你求出最优划分方案下,小明的程序的最小运行时间。

输入格式

由于本题的数据范围较大,部分测试点的 a i a_i ai 将在程序内生成。

第一行两个整数 n , t y p e n, type n,type n n n 的意义见题目描述, t y p e type type 表示输入方式。

  1. t y p e = 0 type = 0 type=0,则该测试点的 a i a_i ai 直接给出。输入文件接下来:第二行 n n n 个以空格分隔的整数 a i a_i ai,表示每组数据的规模。
  2. t y p e = 1 type = 1 type=1,则该测试点的 a i a_i ai特殊生成,生成方式见后文。输入文件接下来:第二行六个以空格分隔的整数 x , y , z , b 1 , b 2 , m x, y, z, b_1, b_2, m x,y,z,b1,b2,m。接下来 m m m 行中,第 i ( 1 ≤ i ≤ m ) i (1 \leq i \leq m) i(1im) 行包含三个以空格分隔的正整数 p i , l i , r i p_i, l_i, r_i pi,li,ri

对于 t y p e = 1 type = 1 type=1 的 23~25 号测试点, a i a_i ai 的生成方式如下:

给定整数 x , y , z , b 1 , b 2 , m x, y, z, b_1, b_2, m x,y,z,b1,b2,m,以及 m m m 个三元组 ( p i , l i , r i ) (p_i, l_i, r_i) (pi,li,ri)

保证 n ≥ 2 n \geq 2 n2。若 n > 2 n \gt 2 n>2,则 ∀ 3 ≤ i ≤ n , b i = ( x × b i − 1 + y × b i − 2 + z ) m o d    2 30 \forall 3 \leq i \leq n, b_i = (x \times b_{i−1} + y \times b_{i−2} + z) \mod 2^{30} ∀3in,bi=(x×bi1+y×bi2+z)mod230

保证 1 ≤ p i ≤ n , p m = n 1 \leq p_i \leq n, p_m = n 1pin,pm=n。令 p 0 = 0 p_0 = 0 p0=0,则 p i p_i pi 还满足 ∀ 0 ≤ i < m \forall 0 \leq i \lt m ∀0i<m p i < p i + 1 p_i \lt p_{i+1} pi<pi+1

对于所有 1 ≤ j ≤ m 1 \leq j \leq m 1jm,若下标值 i ( 1 ≤ i ≤ n ) i (1 \leq i \leq n) i(1in)满足 p j − 1 < i ≤ p j p_{j−1} \lt i \leq p_j pj1<ipj,则有

a i = ( b i m o d    ( r j − l j + 1 ) ) + l j a_i = \left(b_i \mod \left( r_j − l_j + 1 \right) \right) + l_j ai=(bimod(rjlj+1))+lj

上述数据生成方式仅是为了减少输入量大小,标准算法不依赖于该生成方式。

输出格式

输出一行一个整数,表示答案。

样例 #1

样例输入 #1

5 0
5 1 7 9 9

样例输出 #1

247

样例 #2

样例输入 #2

10 0
5 6 7 7 4 6 2 13 19 9

样例输出 #2

1256

样例 #3

样例输入 #3

10000000 1
123 456 789 12345 6789 3
2000000 123456789 987654321
7000000 234567891 876543219
10000000 456789123 567891234

样例输出 #3

4972194419293431240859891640

提示

【样例 1 解释】

最优的划分方案为 { 5 , 1 } , { 7 } , { 9 } , { 9 } \{5,1\}, \{7\}, \{9\}, \{9\} {5,1},{7},{9},{9}。由 5 + 1 ≤ 7 ≤ 9 ≤ 9 5 + 1 \leq 7 \leq 9 \leq 9 5+1799 知该方案合法。

答案为 ( 5 + 1 ) 2 + 7 2 + 9 2 + 9 2 = 247 (5 + 1)^2 + 7^2 + 9^2 + 9^2 = 247 (5+1)2+72+92+92=247

虽然划分方案 { 5 } , { 1 } , { 7 } , { 9 } , { 9 } \{5\}, \{1\}, \{7\}, \{9\}, \{9\} {5},{1},{7},{9},{9} 对应的运行时间比 247 247 247 小,但它不是一组合法方案,因为 5 > 1 5 \gt 1 5>1

虽然划分方案 { 5 } , { 1 , 7 } , { 9 } , { 9 } \{5\}, \{1,7\}, \{9\}, \{9\} {5},{1,7},{9},{9} 合法,但该方案对应的运行时间为 251 251 251,比 247 247 247 大。

【样例 2 解释】

最优的划分方案为 { 5 } , { 6 } , { 7 } , { 7 } , { 4 , 6 , 2 } , { 13 } , { 19 , 9 } \{5\}, \{6\}, \{7\}, \{7\}, \{4,6,2\}, \{13\}, \{19,9\} {5},{6},{7},{7},{4,6,2},{13},{19,9}

【数据范围】

测试点编号 n ≤ n \leq n a i ≤ a_i \leq ai t y p e = type = type=
1 ∼ 3 1 \sim 3 13 10 10 10 10 10 100
4 ∼ 6 4 \sim 6 46 50 50 50 1 0 3 10^3 1030
7 ∼ 9 7 \sim 9 79 400 400 400 1 0 4 10^4 1040
10 ∼ 16 10 \sim 16 1016 5000 5000 5000 1 0 5 10^5 1050
17 ∼ 22 17 \sim 22 1722 5 × 1 0 5 5 \times 10^5 5×105 1 0 6 10^6 1060
23 ∼ 25 23 \sim 25 2325 4 × 1 0 7 4 \times 10^7 4×107 1 0 9 10^9 1091

对于 t y p e = 0 type=0 type=0的所有测试点,保证最后输出的答案 ≤ 4 × 1 0 18 \leq 4 \times 10^{18} 4×1018

所有测试点满足: t y p e ∈ { 0 , 1 } type \in \{0,1\} type{0,1} 2 ≤ n ≤ 4 × 1 0 7 2 \leq n \leq 4 \times 10^7 2n4×107 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1ai109 1 ≤ m ≤ 1 0 5 1 \leq m \leq 10^5 1m105 1 ≤ l i ≤ r i ≤ 1 0 9 1 \leq l_i \leq r_i \leq 10^9 1liri109 0 ≤ x , y , z , b 1 , b 2 < 2 30 0 \leq x,y,z,b_1,b_2 \lt 2^{30} 0x,y,z,b1,b2<230

贪心、dp - TLE 64pts

一个简单的想法是状态 ( i , j ) (i,j) (i,j)表示前i个数,最后一个区间和为j的最小平方和。需优化。

见数据量猜测是贪心,研究样例能猜测:前i个数的最优方案,若最后一个区间是 [ j , i ] [j,i] [j,i],则前面的划分方法与前j-1个数的方案相同。
如果没有递增的限制,这个猜想显然成立。但它害怕一种情况:前j-1个数其他方案与最优方案相比,最后一个区间和更小,也就是对后面的限制更少。
考虑最优方案是.../xx/xxx,另有方案2.../xxx/xx. 利用“最优”这一性质。为何倒数第三个x被分到后一个会更好?
考察将新的数x加入 x 1 , . . . , x n x_1,...,x_n x1,...,xn中的和平方增量,为 x 2 + 2 x ∑ x i x^2+2x\sum{x_i} x2+2xxi. 所以上面的实例中,前两个数和比后两个数和更大。方案2不成立。
所以,最优方案同时也是对后面限制最小的方案。状态减少一个维度。

d p ( i ) dp(i) dp(i)为前i个数的最小答案, f ( i ) f(i) f(i)为前i个数最优方案中最后一个区间和。
d p ( i ) = m i n { d p ( j ) + s 2 ( j + 1 , i ) } , f ( j ) ≤ s ( j + 1 , i ) = f ( i ) . dp(i)=min\{dp(j)+s^2(j+1,i)\}, f(j)\leq s(j+1,i)=f(i). dp(i)=min{dp(j)+s2(j+1,i)},f(j)s(j+1,i)=f(i).

memset(dp, 0x3f, sizeof(dp));
f[0] = 0; dp[0] = 0;
f[1] = a[1]; dp[1] = a[1] * a[1];
for (int i = 2; i <= n; ++i) {
	for (int j = 0; j < i; ++j) {
		if (f[j] <= a[i] - a[j] && ckmin(dp[i], dp[j] + (a[i] - a[j]) * (a[i] - a[j]))) f[i] = a[i] - a[j];
	}
}
printf("%lld\n", dp[n]);

时间复杂度 O ( n 2 ) O(n^2) O(n2).

分析 - TLE 64pts

对于保证答案不超过long long的数据量,计算过程中可能涉及非最优解,dp数组可能爆long long.

刚刚的结论,也是在说,决策点j是使 f ( j ) ≤ s ( j + 1 , i ) f(j)\leq s(j+1,i) f(j)s(j+1,i),即 f ( j ) + s ( j ) ≤ s ( i ) f(j)+s(j)\leq s(i) f(j)+s(j)s(i)的最大的j.
所以不再需要打擂台。可以保存 p o s ( i ) pos(i) pos(i) d p ( i ) dp(i) dp(i)转移到的决策点j, 最后简单地统计答案。

f[0] = 0;
f[1] = a[1];
for (int i = 2; i <= n; ++i) {
	for (int j = i - 1; ~j; --j) {
		if (f[j] <= a[i] - a[j]) {
			f[i] = a[i] - a[j];
			pos[i] = j;
			break;
		}
	}
}
for (int i = n; i; i = pos[i]) {
	ans += f[i] * f[i];
}
printf("%lld\n", ans);

因为s(前缀和)递增,所以pos也递增。找 p o s ( i ) pos(i) pos(i),可以从 p o s ( i − 1 ) pos(i-1) pos(i1)往后找,但这并没有优化算法复杂度。

f[0] = 0;
f[1] = a[1];
for (int i = 2; i <= n; ++i) {
	for (int j = pos[i - 1]; j < i; ++j) {
		if (f[j] <= a[i] - a[j]) {
			f[i] = a[i] - a[j];
			pos[i] = j;
		}
	}
}
for (int i = n; i; i = pos[i]) {
	ans += f[i] * f[i];
}
printf("%lld\n", ans);

时间复杂度 O ( n 2 ) O(n^2) O(n2)

单调队列 - 缺高精 88pts

瓶颈在求满足 f ( j ) + s ( j ) ≤ s ( i ) f(j)+s(j)\leq s(i) f(j)+s(j)s(i)的最大的j.
右边递增,随机生成数据发现左边没有单调性。

对于一个j, f ( j ) + s ( j ) f(j)+s(j) f(j)+s(j)越小,越有竞争力(当然还要考虑j的大小)。如果一个j较小,f(j)+s(j)却较大(也就是逆序对),那么这个j就失去了竞争力。这正是单调队列的特点

时间复杂度 O ( n ) O(n) O(n).

f[0] = 0;
f[1] = a[1];
q.push_back(0); q.push_back(1);
for (int i = 2; i <= n; ++i) {
	int j;
	while (!q.empty() && getval(q.front()) <= a[i]) {
		j = q.front();
		q.pop_front();
	}
	q.push_front(j);
	f[i] = a[i] - a[j]; pos[i] = j;
	while (!q.empty() && getval(q.back()) >= getval(i)) q.pop_back();
	q.push_back(i);
}
for (int i = n; i; i = pos[i]) {
	ans += f[i] * f[i];
}
printf("%lld\n", ans);

高精度 - MLE

如果把a, f数组改为高精度,会爆空间。

空间优化 - TLE、MLE 88pts (补)

仔细想一想,需要高精度的只有最后的统计答案。只需要在最后用临时高精度变量,不用高精度数组

大数据量依旧MLE,可能是源于deque. 奇怪的是,若把生成数据使用的b数组从long long改成int,则MLE变成TLE.

struct Huge {
	ll num[MAXL];
	Huge() {
		memset(num, 0, sizeof(num));
		num[0] = 1;
	}
	void put() {
		for (int i = num[0]; i; --i) {
			printf("%lld", num[i]);
		}
	}
	Huge operator + (const ll &x) const {
		Huge ans;
		for (int i = 1; i <= num[0]; ++i) ans.num[i] = num[i];
		ans.num[1] += x;
		while (ans.num[ans.num[0]] > 10) {
			ans.num[ans.num[0] + 1] = ans.num[ans.num[0]] / 10;
			ans.num[ans.num[0]] %= 10;
			++ans.num[0];
		}
		return ans;
	}		
	Huge operator + (const Huge &x) const {
		Huge ans;
		int len = max(num[0], x.num[0]);
		for (int i = 1; i <= len; ++i) {
			ans.num[i] += num[i] + x.num[i];
			ans.num[i + 1] += ans.num[i] / 10;
			ans.num[i] %= 10;
		}
		if (ans.num[len + 1]) ++len;
		ans.num[0] = len;
		return ans;
	}
	Huge operator * (const Huge &x) const {
		Huge ans;
		int len = num[0] + x.num[0] - 1;
		for (int i = 1; i <= num[0]; ++i) {
			for (int j = 1; j <= x.num[0]; ++j) {
				ans.num[i + j - 1] += num[i] * x.num[j];
				ans.num[i + j] += ans.num[i + j - 1] / 10;
				ans.num[i + j - 1] %= 10;
			}
		}
		while (ans.num[len + 1]) ++len;
		while (len > 1 && !ans.num[len]) --len;
		ans.num[0] = len;
		return ans;
	}
};

int n, type, m, pos[MAXN];
ll a[MAXN], f[MAXN], b[MAXN];
Huge ans;
deque<int> q;

inline ll getval(int i) {
	return f[i] + a[i];
}

int main() {
	scanf("%d%d", &n, &type);
	if (type) {
		ll x, y, z;
		scanf("%lld%lld%lld%lld%lld%d", &x, &y, &z, b + 1, b + 2, &m);
		for (int i = 3; i <= n; ++i) b[i] = (x * b[i - 1] % MOD + y * b[i - 2] % MOD + z) % MOD;
		for (int i = 1, j = 1; i <= m; ++i) {
			int p, l, r; scanf("%d%d%d", &p, &l, &r);
			for (; j <= p; ++j) {
				a[j] = b[j] % (r - l + 1) + l;
				a[j] += a[j - 1];
			}
		}
	}
	else {
		for (int i = 1; i <= n; ++i) {
			scanf("%lld", a + i);
			a[i] += a[i - 1];
		}
	}
	
	f[0] = 0;
	f[1] = a[1];
	q.push_back(0); q.push_back(1);
	for (int i = 2; i <= n; ++i) {
		int j;
		while (!q.empty() && getval(q.front()) <= a[i]) {
			j = q.front();
			q.pop_front();
		}
		q.push_front(j);
		f[i] = a[i] - a[j]; pos[i] = j;
		while (!q.empty() && getval(q.back()) >= getval(i)) q.pop_back();
		q.push_back(i);
	}
	for (int i = n; i; i = pos[i]) {
		Huge tmp; tmp = tmp + f[i];
		ans = ans + tmp * tmp;
	}
	ans.put(); printf("\n");
	
	return 0;
}

滚动数组、压位 - AC

生成数据使用的b可以滚动数组。
高精度压位。

struct Huge {
	ll num[MAXL];
	Huge() {
		memset(num, 0, sizeof(num));
		num[0] = 1;
	}
	void put() {
		printf("%lld", num[num[0]]);
		for (int i = num[0] - 1; i; --i) {
			printf("%09lld", num[i]); //中间的0
		}
	}
	Huge operator + (const ll &x) const {
		Huge ans;
		for (int i = 1; i <= num[0]; ++i) ans.num[i] = num[i];
		ans.num[1] += x;
		while (ans.num[ans.num[0]] > BASE) {
			ans.num[ans.num[0] + 1] = ans.num[ans.num[0]] / BASE;
			ans.num[ans.num[0]] %= BASE;
			++ans.num[0];
		}
		return ans;
	}		
	Huge operator + (const Huge &x) const {
		Huge ans;
		int len = max(num[0], x.num[0]);
		for (int i = 1; i <= len; ++i) {
			ans.num[i] += num[i] + x.num[i];
			ans.num[i + 1] += ans.num[i] / BASE;
			ans.num[i] %= BASE;
		}
		if (ans.num[len + 1]) ++len;
		ans.num[0] = len;
		return ans;
	}
	Huge operator * (const Huge &x) const {
		Huge ans;
		int len = num[0] + x.num[0] - 1;
		for (int i = 1; i <= num[0]; ++i) {
			for (int j = 1; j <= x.num[0]; ++j) {
				ans.num[i + j - 1] += num[i] * x.num[j];
				ans.num[i + j] += ans.num[i + j - 1] / BASE;
				ans.num[i + j - 1] %= BASE;
			}
		}
		while (ans.num[len + 1]) ++len;
		while (len > 1 && !ans.num[len]) --len;
		ans.num[0] = len;
		return ans;
	}
};

int n, type, m, pos[MAXN];
ll a[MAXN], f[MAXN], b[3];
Huge ans;
deque<int> q;

inline ll getval(int i) {
	return f[i] + a[i];
}

int main() {
	scanf("%d%d", &n, &type);
	if (type) {
		ll x, y, z;
		scanf("%lld%lld%lld%lld%lld%d", &x, &y, &z, b + 1, b + 2, &m);
		for (register int i = 1, j = 1; i <= m; ++i) {
			int p, l, r; scanf("%d%d%d", &p, &l, &r);
			for (; j <= p; ++j) {
				if (j > 2) b[j % 3] = (x * b[(j + 2) % 3] % MOD + y * b[(j + 1) % 3] % MOD + z) % MOD;
				a[j] = b[j % 3] % (r - l + 1) + l;
				a[j] += a[j - 1];
			}
		}
	}
	else {
		for (int i = 1; i <= n; ++i) {
			scanf("%lld", a + i);
			a[i] += a[i - 1];
		}
	}
	
	f[0] = 0;
	f[1] = a[1];
	q.push_back(0); q.push_back(1);
	for (register int i = 2; i <= n; ++i) {
		int j;
		while (!q.empty() && getval(q.front()) <= a[i]) {
			j = q.front();
			q.pop_front();
		}
		q.push_front(j);
		f[i] = a[i] - a[j]; pos[i] = j;
		while (!q.empty() && getval(q.back()) >= getval(i)) q.pop_back();
		q.push_back(i);
	}
	for (register int i = n; i; i = pos[i]) {
		Huge tmp; tmp = tmp + f[i];
		ans = ans + tmp * tmp;
	}
	ans.put(); printf("\n");
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值