GPT o4-mini 用 1m10s 战胜了 P9400
https://www.luogu.com.cn/record/216185866
思考动态规划解法
我在考虑递推式,其中dp[i]表示有效序列的数量,但需要处理无效的部分。虽然包含排除法可以用来解决这个问题,但感觉太麻烦了。实际上,典型的动态规划使用的是前缀和,即dp[i]可以通过sumDp[i]计算。我还在考虑其他可能的解法,像是将序列里的每个段看作一个L运行的部分,再拆分它们。这个问题有些难,但我会继续推理一下,试图找到更简洁的解法。
优化动态规划
我正在考虑一种新的动态规划解法,想通过滑动窗口优化求解速度,减少不必要的重复计算。关键是要将潜在的无效段处理成包含排除的方法,或者用段树卷积来优化。尽管这样可能提高了速度,复杂度依然很高。其实我还在考虑是否能借助有序窗口或矩阵指数加速计算,但具体如何实现并不太明确,得在细节上继续琢磨才能找到最佳方案。
优化动态规划方案
我在考虑如何优化这里的DP算法,主要是解决计算复杂度的问题,特别是当a很大时,O(n*a)会变得太慢。其中一种思路是,通过减去包含无效段的序列,也就是那些包含长度为a的H块,我们可以设法优化此过程。对于每个i,我可以根据之前的序列计算,并逐步构建出有效序列的数量。这个方法应该能更好地控制复杂度。
动态规划推导过程
我在调整动态规划的状态转移。重点在如何避免让已经违反条件的序列重新计算。如果序列在dp[i]生成时已经违反了规则,那么它的扩展应该被排除。特别是当我们延伸一个有H的子序列时,是否需要在之前就已经减去其中不符合条件的部分。这个推导过程稍显复杂,但通过仔细思考这些递归关系,我认为这个方法有可能是有效的。
已思考 1m 10s
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
// fast exponentiation modulo MOD
ll modpow(ll a, ll e=MOD-2) {
ll r = 1;
while (e) {
if (e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, a;
ll b;
cin >> n >> a >> b;
vector<ll> L(n+1), R(n+1), T(n+1);
for(int i = 1; i <= n; i++){
ll li, ri;
cin >> li >> ri;
// count of choices <= b
ll up = min(ri, b);
L[i] = (up >= li ? (up - li + 1) % MOD : 0);
// count of choices > b
ll lo = max(li, b+1);
R[i] = (ri >= lo ? (ri - lo + 1) % MOD : 0);
T[i] = (L[i] + R[i]) % MOD;
}
// dp-state array of size a, stored in a rolling circular buffer
vector<ll> A(a, 0);
vector<int> version(a, 0);
int cur_ver = 1;
ll S = 1; // sum of dp[k] over k=0..a-1
ll g = 1; // global multiplier for dp-states
ll inv_g = 1; // inverse of g
int head = 0; // circular buffer head, A[head] holds dp[0] / g
// initial dp: dp[0]=1
A[0] = 1;
version[0] = cur_ver;
for(int i = 1; i <= n; i++){
// retrieve dp_old[a-1]
int idx_last = (head + (a - 1)) % a;
ll old_dp_am1 = 0;
if (version[idx_last] == cur_ver) {
old_dp_am1 = A[idx_last] * g % MOD;
}
ll new_dp0 = S * L[i] % MOD;
if (R[i] == 0) {
// if R_i == 0, all H-extensions die => only dp[0] survives
S = S * L[i] % MOD;
// reset the dp-buffer
cur_ver++;
head = 0;
g = 1;
inv_g = 1;
// store new dp[0]
A[0] = new_dp0;
version[0] = cur_ver;
} else {
// general case
// update total sum S
S = ( (S * T[i]) % MOD - (old_dp_am1 * R[i]) % MOD + MOD ) % MOD;
// update global multiplier
g = g * R[i] % MOD;
ll inv_R = modpow(R[i]);
inv_g = inv_g * inv_R % MOD;
// shift head backwards by 1 in circular buffer
head = (head - 1 + a) % a;
// store new dp[0] as A[head] = new_dp0 / g
A[head] = new_dp0 * inv_g % MOD;
version[head] = cur_ver;
}
}
cout << S << "\n";
return 0;
}
说明:
-
我们将状态分为 \(dp[k]\) 表示以恰好 \(k\) 个 “高亮” (亮度 \(>\!b\)) 结尾的方案权重,总共只需维护 \(k=0,1,\dots,a-1\)。
-
设 \(S=\sum_{k=0}^{a-1}dp[k]\)。第 \(i\) 间宿舍可选 “低” (\(\le b\)) 的方案数为 \(L_i\),可选 “高” 的方案数为 \(R_i\),总数 \(T_i=L_i+R_i\)。
-
转移:
- 新的 \(dp[0]\)(以“低”结尾)来自任意前缀后选“低”,权重 \(S \times L_i\);
- 对于 \(k\ge1\),新的 \(dp[k]\)(以 \(k\) 个“高”结尾)来自旧的 \(dp[k-1]\) 再选“高”,权重乘以 \(R_i\);
- 超过 \(a-1\) 个“高”即违规,不纳入。
-
维护一个环形缓冲区存储 \(\{dp[0],\dots,dp[a-1]\}\) 除以一个全局缩放因子 \(g\),这样“批量乘以 \(R_i\)”只需更新 \(g\) 即可,按指针移动实现整体右移,插入新 \(dp[0]\),并从总和 \(S\) 中减去最旧的那条“将要越界”的状态贡献。
-
如果某步 \(R_i=0\),所有以“高”结尾的方案都归零,只剩以“低”结尾的 \(dp[0]\),此时直接重置缓冲即可。

浙公网安备 33010602011771号