P11301 [NOISG 2021 Finals] Password
可以先从 dp 的角度去思考。
首先考虑怎么维护修改。
我们把对于一个点的修改分为从前面一个位置继承的修改和从这个点开始的修改。
我们设 \(dp_{i,j}\) 表示考虑到第 \(i\) 位有 \(j\) 次修改。
转移和状态数都太多了,但是显然,对于一个点,其合法状态必定只有 \(j\) 为 \(v+t(k+1)\) 时,且 \(t\) 为非负整数。
\(v\) 是好求的,计算初始状态与最终状态模意义下只差即可。
重新设置状态 \(dp_{i,j}\) 表示在 \(i\) 有 \(v+j(k+1)\) 次修改。
我们考虑有什么转移。
首先,考虑从 \(dp_{i-1,j}\) 转移到 \(dp_{i,j}\) 的情况,这是无条件的,如果新的值比之前的值小,无需添加,否则可能有额外代价。
如果我们从 \(dp_{i-1,j}\) 转移到 \(dp_{i,j-1}\) 那么我们可以直接把之前的全部拿出来用,不用额外代价,非常好。如果再消耗 \(j\) 的话就没有任何收益,所以最多只会消耗一个。
如果我们从 \(dp_{i-1,j}\) 转移到 \(dp_{i,j+1}\) 那么我们同样计算代价即可,只有在这个值大于前面的值时代价才会小于 \(k+1\)。否则我们没有必要让 \(j\) 继续变大,因为这个代价不可能小于收益。
那么我们就可以分为让 \(j\) 加一、减一和不变的情况。
加一必须在减一前面,类似一个括号匹配问题。
那么我们从后往前遍历,初始默认不变考虑一个点作为加一的代价,如果这个点加一之后找后面的一个点匹配起来更优,则修改,否则将减一后的代价变化量丢进堆里,以便维护。
剩下还有一点问题,可能在这个点加一不是最优的,在前面一点的位置有代价更小的,需要替换。那么一个加一可以选择让后面一个从不变变成减一,也可以让加一变成不变,都丢到堆里即可。
一个加一是否可能会变成减一?肯定不会,因为加一有效的一个必然条件是不变时代价为零,所以没有可能存在这种情况的更优解。
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, k, a[300005], b[300005], val[300005];
priority_queue<int> q, q2;
signed main() {
freopen("pwd.in", "r", stdin);
freopen("pwd.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> k;
k++;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n; i++) val[i] = ((b[i] - a[i]) % k + k) % k;
int ans = 0;
for (int i = n; i >= 1; i--) {
int v1 = max(0ll, val[i] - val[i - 1]);
int v3 = val[i] + k - val[i - 1];
int tmp;
if (!q.empty())
tmp = q.top();
else
tmp = 0;
if (v3 - tmp < v1) {
ans += v3 - tmp;
if (!q.empty())
q.pop();
q.push(v3);
} else
ans += v1, q.push(v1);
}
cout << ans;
return 0;
}

浙公网安备 33010602011771号