【校内训练2019-02-19】小b爱取模

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

【思路要点】

  • 考虑数列的差分数组,我们希望通过尽可能少的对差分数组的的前后两个位置 i,j (i&lt;j)i,j\ (i&lt;j)i,j (i<j) 分别进行 +1,−1+1,-1+1,1 的操作使得其变成一个模意义下的全 000 数组。
  • 对于 k=2,3k=2,3k=2,3 的情况,简单设计贪心即可,具体可见代码。
  • 一个值得注意的性质是,一个位置 iii 不可能既被 +1+1+1 操作过,又被 −1-11 操作过,否则,这两次操作可以并做一次。
  • 由此,我们可以设计一个 O(N2)O(N^2)O(N2) 的动态规划。
  • 我们可以将每一个数 x (x=1,2,3)x\ (x=1,2,3)x (x=1,2,3) 转化为若干个区间左端点或是若干个区间右端点,可以发现,转化为区间左端点的个数为 4−x4-x4x ,区间右端点的个数为 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+4aidpi+1,j+4ai
    dpi,j⇒dpi+1,j−ai (j−ai≥0)dp_{i,j}\Rightarrow dp_{i+1,j-a_i}\ (j-a_i≥0)dpi,jdpi+1,jai (jai0)
  • 观察该动态规划的过程,我们可以发现,计数器 jjj 在每一点 iii 处均会减少 aia_iai ,而我们可以选择对其进行 +4+4+4 操作,每次代价为 4−ai4-a_i4ai ,需保证 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值