题解:Luogu P5665 [CSP-S2019] 划分
一些闲话
毒瘤题!!!时空都卡。难以想象 2019 年的考生作何感想。
简述题意
给定长度为 \(n\) 的序列 \(a\)。你需要找到一些分界点 \(1 \leq k_1 \lt k_2 \lt \cdots \lt k_p \lt n\),使得
在此基础上,最小化
对于所有数据,\(2\leq n\leq 4\times 10^7\),\(1\leq a_i\leq 10^9\)。
题解
算法 1
暴力 DP。令 \(f_{i,j}\) 表示考虑 \(a[1,i]\),且最后一段为 \(a[j+1,i]\) 的最小价值。转移时直接枚举倒数第二段的划分点并保证合法即可。时间复杂度 \(O(n^3)\)。
算法 2
考虑贪心。
我们有一个性质:在最优划分方案中,最后一段的元素和必然是所有合法划分方案中最小的。由各段元素和具有单调性,证明显然。
于是可以优化算法 1。这时候我们改变状态,令 \(f_i\) 表示考虑 \(a[1,i]\),且最后一段为 \([f_i+1,i]\)。再设 \(g_i=s_i-s_{f_i}\),即最优划分方案中最后一段的元素和。考察一个位置 \(j\) 能够作为决策点的条件:
所以我们在转移时,暴力枚举决策点,找到满足上述条件且最靠右的 \(j\)。时间复杂度 \(O(n^2)\)。
正解
显然
所以对每个位置维护这个 \(val_j=g_j+s_j\),随便套个数据结构就能做到 \(O(n\log n)\),但依然会被卡掉。所以我们需要(均摊)\(O(1)\) 的转移。
考虑单调队列优化。对于两个合法决策点 \(i,j\),我们只需要保留较大的那一个,所以我们维护一个下标和对应的 \(val\) 均单调递增的单调队列。每次循环一个 \(i\),若 \(val_{q_{head+1}}\leq s_i\),就不断弹出队头。决策点就是最终合法的队头或 \(0\)。加入一个新的决策时,平凡地维护单调性即可。时间复杂度 \(O(n)\)。
当然,这题十分毒瘤,空间和时间都卡得很死,需要拼命优化。空间优化上,不要开高精数组,一个多余的 \(O(n)\) 数组都不要开。时间优化上,快读肯定要上的,高精压位压到 \(8\) 位应该够用了。
代码
#include <iostream>
#include <iomanip>
using namespace std;
#define lowbit(x) ((x) & -(x))
#define add_mod(x, v) (x) = ((ll)(x) + (v)) % MOD
#define mul_mod(x, v) (x) = (1ll * (x) * (v)) % MOD
#define sub_mod(x, v) (x) = (((ll)(x) - (v)) % MOD + MOD) % MOD
#define chk_min(x, v) (x) = min((x), (v))
#define chk_max(x, v) (x) = max((x), (v))
typedef long long ll;
typedef pair<int, int> pii;
const int MAX_N = 4e7 + 5, MAX_BITS = 4 + 5, BIT = (1 << 30) - 1, C = 1e8;
int n, tp;
ll f[MAX_N], a[MAX_N];
int l = 1, r = 0;
ll q[MAX_N];
ll calc(int i) { return a[i] - a[f[i]] + a[i]; }
ll read() {
int s = 1; char ch;
for (ch = getchar(); (ch < '0' || ch > '9') && ch != EOF; ch = getchar())
if (ch == '-') s = -1;
ll x = ch - '0';
for (ch = getchar(); ch >= '0' && ch <= '9' && ch != EOF; ch = getchar()) x = x * 10 + (ch ^ 48);
return x * s;
}
struct BigInt {
ll d[MAX_BITS];
void init(ll x = 0) {
for (int i = 0; i <= MAX_BITS - 5; ++i) d[i] = 0;
for (int i = 0; x; ++i, x /= C) d[i] = x % C;
}
BigInt &operator+=(BigInt &x) {
for (int i = 0; i <= MAX_BITS - 5; ++i)
if ((d[i] += x.d[i]) >= C) ++d[i + 1], d[i] -= C;
return *this;
}
BigInt operator*(BigInt &x) const {
BigInt res; res.init();
for (int i = 0; i <= MAX_BITS - 5; ++i)
for (int j = 0; j <= i; ++j)
if ((res.d[i] += d[j] * x.d[i - j]) >= C)
res.d[i + 1] += res.d[i] / C, res.d[i] %= C;
return res;
}
friend ostream &operator<<(ostream &s, const BigInt &x) {
int i;
for (i = MAX_BITS - 5; i >= 0 && !x.d[i]; --i);
if (i < 0) return s << 0;
s << x.d[i--];
while (i >= 0) s << setw(8) << setfill('0') << x.d[i--];
return s;
}
} ans, sum;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
n = read(); tp = read();
if (!tp) for (int i = 1; i <= n; ++i) a[i] = read();
else {
int x, y, z, b1, b2, m;
x = read(); y = read(); z = read();
b1 = read(); b2 = read(); m = read();
for (int i = 1, j = 1; j <= m; ++j) {
int p, l, r; p = read(); l = read(); r = read();
while (i <= p) {
int b;
if (i == 1) b = b1;
else if (i == 2) b = b2;
else {
b = (((1ll * x * b2) & BIT) + ((1ll * y * b1) & BIT)) & BIT;
(b += z) &= BIT;
}
a[i] = b % (r - l + 1) + l;
if (i >= 3) b1 = b2, b2 = b;
++i;
}
}
}
for (int i = 2; i <= n; ++i) a[i] += a[i - 1];
f[1] = 0; q[++r] = 1;
for (int i = 2; i <= n; ++i) {
while (l < r && calc(q[l + 1]) <= a[i]) ++l;
f[i] = (l <= r && calc(q[l]) <= a[i] ? q[l] : 0);
while (l <= r && calc(q[r]) >= calc(i)) --r;
q[++r] = i;
}
int p = n;
while (p) {
sum.init(a[p] - a[f[p]]);
sum = sum * sum; ans += sum;
p = f[p];
}
cout << ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现