Loading

划分

首先大家应该都能想到一个朴素的 dp: \(f_i, j\) 表示最后一段是 \([j, i]\) 时最小的代价,转移显然,时间复杂度 \(\mathcal{O}(n^3)\), 期望得分 \(36\)

然后考虑优化,发现 dp 没啥用数据结构优化的前途,但是这题是 dp,所以大概率需要我们发现一些题目的性质,得到贪心的方法,从而省去不优的状态。

有如下两个观察:

  1. 一段和为 \(a + b\) 的数如果切割成一段和为 \(a\) 的与一段和为 \(b\) 的仍满足答案,则由于 \((a + b)^2 > a^2 + b^2\), 且 \(b < a + b\), 不会使后续划分变差,所以这一定是不劣的。

  2. 如果一段数划分为了一段和为 \(a\) 的以及一段和为 \(b + x\) 的,且 \(a + x \le b\), 我们可以取出 \(b\) 中一段和为 \(x\) 的放入 \(a\) 中,根据 \(a \le b\) 得到 \((a + x) ^ 2 + b^2 < a + (b + x)^2\), 这样调整一定更优。

综合以上观察我们发现:每个 \(i\) 选择的决策点 \(j\)(即划分出 \([j, i]\))一定是最大的满足条件的 \(j\), 因为如果决策点是 \(k\)\(k < j\), 那么完全可以将 \([k + 1, j]\) 这段放在前面而不是划分到 \(i\) 这一段,而放在前面的情况已经在 \(j\) 处考虑过了。如果决策点是 \(k\)\(k > j\), 那么根据观察一多划分肯定更优。

由此,我们设计出了一个 \(\mathcal{O}(n^2)\) 的 dp,记决策点为 \(las_i\), 前缀和数组为 \(s\), 转移为

\[las_i = \max j \space s.t. s_i - s_j \ge s_j - s_{las_j} \]

如何优化?对于上面的条件,移项得到

\[s_i \ge 2s_j - s_{las_j} \]

\(val_j = 2s_j - s_{las_j}\), 我们发现如果 \(k < j\)\(val_j \le s_i \lor val_j \le val_k\), \(k\) 就一定不如 \(j\) 优。

这个过程想到了什么?单调队列。只不过是把弹出队首的条件改了一下而已。

最后的统计答案就每次跳 \(las\) 即可。

时间复杂度 \(\mathcal{O}(n)\)

下面是一些细节:

  1. 注意开 __int128
  2. 这题卡空间,long long 类型的数组能且只能开一个,这里只开前缀和数组,将 \(val\) 数组用前缀和 \(\mathcal{O}(1)\) 计算即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 4e7 + 10, M = 1e5 + 10, mod = (1 << 30) - 1;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
    #ifdef ONLINE_JUDGE
        return ;
    #endif
    cout << arg << ' ';
    dbg(args...);
}   
int n, typ, a[N], las[N], b[N], m, p[M], q[N];
ll x, y, z, s[N];
ll calc(int i) { return (s[i] << 1) - s[las[i]]; }

int stk[60], top;
void print(i128 num) {
	while (num) { stk[++top] = num % 10; num /= 10; }
	while (top) cout << stk[top--];
	cout << '\n'; 
} 

int main() {
	// freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> typ;
    if (!typ) {
    	for (int i = 1; i <= n; i++) cin >> a[i];
	} else {
		cin >> x >> y >> z >> b[1] >> b[2] >> m;
		for (int i = 3; i <= n; i++) b[i] = (x * b[i - 1] + y * b[i - 2] + z) & mod;
		for (int i = 1, l, r; i <= m; i++) {
			cin >> p[i] >> l >> r;
			for (int j = p[i - 1] + 1; j <= p[i]; j++) {
				a[j] = b[j] % (r - l + 1) + l;
			}
		}
	}
	
	s[0] = 0;
	for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
	
	int fr = 1, re = 0; q[++re] = 0;
	for (int i = 1; i <= n; i++) {
		while (fr < re && calc(q[fr + 1]) <= s[i]) fr++;
		las[i] = q[fr];
		while (fr <= re && calc(q[re]) >= calc(i)) re--;
		q[++re] = i;
	}
	
	i128 ans = 0; int pos = n;
	
	while (pos) { i128 num = s[pos] - s[las[pos]]; num = num * num; ans += num; pos = las[pos]; }
    
	print(ans);
	return 0;
}
posted @ 2025-12-17 15:49  循环一号  阅读(3)  评论(0)    收藏  举报