P16530 [THUPC 2026 决赛] 积木消除游戏 题解
前言
R圣模拟赛T4原题,虽然按难度排序是T1,考场上写了一个前缀和优化DP成功挂了70pts,赛后才发现前缀和写我只花了15min左右就AC了,但是由于神秘执念我还是写了一篇DP的题解,而且貌似是目前全网这题唯一DP题解。
题目大意
有 \(n\) 堆积木,第 \(i\) 堆数量为 \(a_i\)。
每次可以选择一段长度至少为 \(k\) 的连续区间,并选择一个模数 \(m \in \{p,q\}\),将区间内所有积木数量对 \(m\) 取余。
可以进行任意多次操作,求最终所有积木总数的最小值。
性质
1:
对任意一堆积木,无论经过多少次取模操作,其最终值一定是下面两种之一:
- \(a_i \bmod p\)
- \((a_i \bmod q) \bmod p\)
因为先对 \(q\) 取模再对 \(p\) 取模,结果不会比直接对 \(p\) 取模更大,反之亦然。
因此每堆积木能达到的最小值是
2:
第一次选的整个区间都必须是同一种,剩下的随便选
第一次操作必然选择一段长度至少为 \(k\) 的连续区间,并对该区间内所有元素使用同一个模数。
在这段区间内,所有元素的最终值至少是它们对那个模数取模的结果。
区间外的元素可以通过后续操作调整到各自的 \(\text{bst}_i\)。
由于任何偏离 \(\text{bst}_i\) 的取值都会使总和增加(增量非负),所以最优方案中:
- 存在一段长度恰好为 \(k\) 的连续区间(长度更长只会让增量变大或不变);
- 该区间内所有元素统一取 \(a_i \bmod p\) 或统一取 \((a_i \bmod q) \bmod p\);
- 其余位置全部取 \(\text{bst}_i\)。
3. 转化
令
则答案
因为窗口内统一取模相较于取 \(\text{bst}_i\) 的增量就是对应的 \(d\) 之和。
实现
对每个 \(i\):
- \(v_1 = a_i \bmod p\)
- \(v_2 = (a_i \bmod q) \bmod p\)
- \(\text{bst} = \min(v_1, v_2)\)
- \(\text{sbt} += \text{bst}\)
- \(d_1[i] = v_1 - \text{bst}\)
- \(d_2[i] = v_2 - \text{bst}\)
用 DP 维护长度为 \(k\) 的滑动窗口和: - \(\text{dp1}[i]\) 表示以 \(i\) 结尾、长度为 \(k\) 的窗口内 \(d_1\) 之和
- \(\text{dp2}[i]\) 表示以 \(i\) 结尾、长度为 \(k\) 的窗口内 \(d_2\) 之和
- 转移:\(\text{dp1}[i] = \text{dp1}[i-1] + d_1[i] - (i>k ? d_1[i-k] : 0)\),\(\text{dp2}\) 同理
取所有窗口中的最小值 \(\text{mn}_1, \text{mn}_2\)。
答案 = \(\text{sbt} + \min(\text{mn}_1, \text{mn}_2)\)。
正确性证明
懒得写了,和前缀和解法大差不差,可以看题解区orR圣的题解(在T4),但是注意到直接滑动窗口时间大爆炸,所以要前缀和\(O(n)\)预处理然后再跑。什么你问我前缀和优化DP我为什么不直接写前缀和,那你别管
复杂度分析
所有 \(n\) 之和不超过 \(10^5\),总复杂度 \(O(\sum n)\)。
码(
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
const ll INF = 1000000000000000000LL;
int n, k;
ll p, q;
ll a[N], d1[N], d2[N], dp1[N], dp2[N];
void solve()
{
cin >> n >> k >> p >> q;
ll sbt = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
ll v1 = a[i] % p;
ll v2 = (a[i] % q) % p;
ll bst = v1;
if (v2 < bst)
bst = v2;
sbt += bst;
d1[i] = v1 - bst;
d2[i] = v2 - bst;
}
ll mn1 = INF, mn2 = INF;
dp1[0] = 0;
dp2[0] = 0;
for (int i = 1; i <= n; i++)
{
dp1[i] = dp1[i - 1] + d1[i];
dp2[i] = dp2[i - 1] + d2[i];
if (i > k)
{
dp1[i] -= d1[i - k];
dp2[i] -= d2[i - k];
}
if (i >= k)
{
if (dp1[i] < mn1)
mn1 = dp1[i];
if (dp2[i] < mn2)
mn2 = dp2[i];
}
}
ll ans = sbt + mn1;
if (mn2 < mn1)
ans = sbt + mn2;
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
while (T--)
solve();
return 0;
}

浙公网安备 33010602011771号