【思路要点】
- 考虑数列的差分数组,我们希望通过尽可能少的对差分数组的的前后两个位置 i,j (i<j)i,j\ (i<j)i,j (i<j) 分别进行 +1,−1+1,-1+1,−1 的操作使得其变成一个模意义下的全 000 数组。
- 对于 k=2,3k=2,3k=2,3 的情况,简单设计贪心即可,具体可见代码。
- 一个值得注意的性质是,一个位置 iii 不可能既被 +1+1+1 操作过,又被 −1-1−1 操作过,否则,这两次操作可以并做一次。
- 由此,我们可以设计一个 O(N2)O(N^2)O(N2) 的动态规划。
- 我们可以将每一个数 x (x=1,2,3)x\ (x=1,2,3)x (x=1,2,3) 转化为若干个区间左端点或是若干个区间右端点,可以发现,转化为区间左端点的个数为 4−x4-x4−x ,区间右端点的个数为 xxx ,将操作次数放在转化为区间左端点的转移上,我们可以得到如下转移:
dpi,j+4−ai⇒dpi+1,j+4−aidp_{i,j}+4-a_{i}\Rightarrow dp_{i+1,j+4-a_i}dpi,j+4−ai⇒dpi+1,j+4−ai
dpi,j⇒dpi+1,j−ai (j−ai≥0)dp_{i,j}\Rightarrow dp_{i+1,j-a_i}\ (j-a_i≥0)dpi,j⇒dpi+1,j−ai (j−ai≥0)- 观察该动态规划的过程,我们可以发现,计数器 jjj 在每一点 iii 处均会减少 aia_iai ,而我们可以选择对其进行 +4+4+4 操作,每次代价为 4−ai4-a_i4−ai ,需保证 jjj 时刻不为负。
- 不难发现在 jjj 减到负数时找到前面一个最大的 aia_iai 进行该操作即可。
- 时间复杂度 O(NK)O(NK)O(NK) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e7 + 5; const int MIDN = 1e3 + 5; const int INF = 1e9; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, k, a[MAXN]; char s[MAXN]; void subtask2() { int cnt = 0; for (int i = 1; i <= n; i++) cnt += a[i]; writeln(cnt / 2); } void subtask3() { bool stage = false; int pos = 1, ans = 0; for (int i = 1; i <= n; i++) { if (a[i] == 0) continue; while (a[i] != 0) { if (stage) { while (a[pos] == 0) pos--; a[pos] = (a[pos] + 2) % k; } else { chkmax(pos, i + 1); while (pos <= n && a[pos] != 1) pos++; if (pos <= n) a[pos] = 0; else { stage = true, pos = n; continue; } } a[i] = (a[i] + 1) % k; ans++; } } writeln(ans); } void subtask4bf() { static int dp[MIDN][MIDN * 3]; for (int i = 0; i <= n; i++) for (int j = 0; j <= 3 * n; j++) dp[i][j] = INF; dp[0][0] = 0; for (int i = 1; i <= n; i++) { for (int j = 0; j <= 3 * n; j++) { if (dp[i - 1][j] == INF) continue; int tmp = dp[i - 1][j]; chkmin(dp[i][j + (4 - a[i])], tmp + 4 - a[i]); if (a[i] <= j) chkmin(dp[i][j - a[i]], tmp); } } writeln(dp[n][0]); } void subtask4() { //subtask4bf(); int cnt[4] = {0, 0, 0, 0}; int sum = 0, ans = 0; for (int i = 1; i <= n; i++) { cnt[a[i]]++; sum -= a[i]; if (sum < 0) { for (int j = 3; sum < 0; j--) if (cnt[j]) { cnt[j]--; sum += 4; ans += 4 - j; } } } writeln(ans); } int main() { freopen("modulo.in", "r", stdin); freopen("modulo.out", "w", stdout); read(k), scanf("%s", s + 1); n = strlen(s + 1), s[0] = s[++n] = '0'; for (int i = 1; i <= n; i++) a[i] = (s[i] - s[i - 1] + k) % k; if (k == 2) subtask2(); if (k == 3) subtask3(); if (k == 4) subtask4(); return 0; }

本文探讨了一种算法,旨在通过最小化操作次数将数列转换为模意义下的全0数组。针对不同模数k=2,3,4k=2,3,4k=2,3,4,提出了贪心策略与动态规划解决方案。对于k=4k=4k=4,采用优化的动态规划方法,避免了负计数器,并在时间复杂度O(NK)O(NK)O(NK)内高效求解。

397

被折叠的 条评论
为什么被折叠?



