← 全部文章

AtCoder Grand Contest 037 题解

本文目录(15 节)
  1. Dividing a String
  2. RGB Balls
  3. Numbers on a Circle
  4. Sorting a Grid
  5. 题意
  6. 题解
  7. 代码
  8. Reversing and Concatenating
  9. 题意
  10. 题解
  11. 代码
  12. Counting of Subarrays
  13. 题意
  14. 题解
  15. 代码

AtCoder Grand Contest 037

Dividing a String

可以证明划分出来的段长度不超过 22,于是 O(n)\mathcal O(n) DP 即可。

const int N = 2e5 + 7, inf = 1e9;
int n, f[N][2];
char s[N];

int main() {
	rds(s, n), f[0][1] = -inf;
	for (int i = 1; i <= n; i++) {
		f[i][0] = f[i][1] = -inf;
		if (i == 1 || s[i] != s[i-1])
			f[i][0] = max(f[i][0], f[i-1][0] + 1);
		f[i][0] = max(f[i][0], f[i-1][1] + 1);
		if (i > 1) f[i][1] = max(f[i][1], f[i-2][0] + 1);
		if (i > 3 && s[i] != s[i-2] && s[i-1] != s[i-3])
			f[i][1] = max(f[i][1], f[i-2][1] + 1);
	}
	print(max(f[n][0], f[n][1]));
	return 0;
}

RGB Balls

最小值直接贪心,方案数把每次的选择数量乘起来,最后乘上 n!n!。

const int N = 3e5 + 7;
int n, r, g, b, rg, gb, br;
char c;
modint ans = 1;

int main() {
	rd(n);
	for (int i = 1; i <= n; i++) ans *= i;
	for (int i = 0; i < 3 * n; i++) {
		rdc(c);
		switch (c) {
			case 'R' : {
				if (gb) ans *= gb--;
				else if (g) ans *= g--, ++rg;
				else if (b) ans *= b--, ++br;
				else ++r;
				break;
			}
			case 'G' : {
				if (br) ans *= br--;
				else if (b) ans *= b--, ++gb;
				else if (r) ans *= r--, ++rg;
				else ++g;
				break;
			}
			case 'B' : {
				if (rg) ans *= rg--;
				else if (r) ans *= r--, ++br;
				else if (g) ans *= g--, ++gb;
				else ++b;
				break;
			}
		}
	}
	print(ans);
	return 0;
}

Numbers on a Circle

倒着考虑,如果一个位置比左右两边都大,那么这个位置上的数必然是通过一次对这个位置的操作得到的。

于是可以每次从最大值开始不断往下减(除),中途判一下无解的情况,时间复杂度大概是 O(nlog⁡nlog⁡w)\mathcal O(n \log n \log w)。

#define Fail return print(-1), 0
const int N = 2e5 + 7;
int n, a[N], b[N];
ll ans;

int main() {
	rd(n), rda(a, n), rda(b, n);
	pq<pi> q;
	for (int i = 1; i <= n; i++)
		if (a[i] != b[i]) q.push(mp(b[i], i));
	while (q.size()) {
		int x = q.top().se, l = x == 1 ? n : x - 1, r = x == n ? 1 : x + 1;
		q.pop();
		int t = (b[x] - max(max(b[l], b[r]), a[x])) / (b[l] + b[r]);
		ans += t, b[x] -= t * (b[l] + b[r]);
		while (b[x] > max(max(b[l], b[r]), a[x])) ++ans, b[x] -= b[l] + b[r];
		if (b[x] < a[x]) Fail;
		if (b[x] != a[x]) q.push(mp(b[x], x));
	}
	print(ans);
	return 0;
}

Sorting a Grid

题意

  • 给定一个 n×mn \times m 的矩阵,每个位置上的数 ∈[1,nm]\in[1,nm],且每个数恰好出现一次。
  • 你可以进行三次操作:
    1. 将每一行重排。
    2. 将每一列重排。
    3. 再将每一行重排。
  • 要求最终矩阵被还原为有序矩阵。
  • n,m≤100n,m \le 100。

题解

倒着考虑,第三步会将同一行的打乱,因此同一行的顺序不重要,于是可以将每个数的行数看成它们的颜色。

第二步会将同一列的打乱,因此第二步之前每一列应该恰好每种颜色各有一个。

于是问题变成了,在一个矩阵中有 nn 种颜色,每种颜色恰好 mm 个,要通过重排每一行,实现每一列恰好每种颜色各一个。

考虑二分图匹配模型,左边为 nn 行,右边为 nn 种颜色,对于每一行的每种颜色连边,

然后找 mm 次最大匹配,将第 ii 的匹配作为第 ii 列。根据 Hall 定理,每次匹配必然是完美匹配。

使用匈牙利算法时间复杂度为 O(n2m2)\mathcal O(n^2m^2)。

代码

const int N = 107;
int n, m, a[N][N], b[N][N], f[N], g[N], v[N], p[N];

bool dfs(int i, int k) {
	for (int j = 1; j <= m; j++)
		if (a[i][j]) {
			int x = (a[i][j] - 1) / m + 1;
			if (v[x] != k) {
				v[x] = k;
				if (!f[x] || dfs(f[x], k))
					return f[x] = i, g[i] = j, 1;
			}
		}
	return 0;
}

int main() {
	rd(n, m);
	for (int i = 1; i <= n; i++)
		rda(a[i], m);
	for (int j = 1; j <= m; j++) {
		for (int i = 1; i <= n; i++)
			dfs(i, (i - 1) * m + (j - 1) + 1);
		for (int i = 1; i <= n; i++)
			b[i][j] = a[i][g[i]], a[i][g[i]] = f[i] = 0;
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			print(b[i][j], " \n"[j==m]);
	for (int j = 1; j <= m; j++) {
		for (int i = 1; i <= n; i++)
			p[(b[i][j]-1)/m+1] = b[i][j];
		for (int i = 1; i <= n; i++)
			b[i][j] = p[i];
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			print(b[i][j], " \n"[j==m]);
	return 0;
}

Reversing and Concatenating

题意

  • 给定一个长度为 nn 的字符串 ss。
  • 你可以进行如下操作 kk 次:
    • 将 ss 变成 ssrss^r 的一个长度为 nn 的子串。
  • 要求最终 ss 的字典序最小。
  • n≤5×103n \le 5 \times 10^3,k≤109k \le 10^9。

题解

另 ss 中最小的字符为 cc,设 ss 中最长连续的 cc 的长度为 ll,则最终的 ss 内我们至少可以构造出开头连续 min⁡(n,2k−1l)\min(n, 2^{k-1}l) 个 cc 的串。

若 min⁡\min 是取到 2k−1l2^{k-1}l,则意味着后面还有一些字符要确定,可以发现后面的字符一定是 ssrss^r 的一个后缀,由于数据范围很小,直接暴力找最小后缀即可,时间复杂度 O(n2)\mathcal O(n^2)。

代码

const int N = 1e4 + 7;
int n, k, a[N], l, w;
char s[N], c, ans[N];

inline void upd(int p) {
	bool ok = 1;
	for (int i = 1; i <= w; i++) {
		if (ans[i] > s[p - i + 1]) break;
		if (ans[i] < s[p - i + 1]) {
			ok = 0;
			break;
		}
	}
	if (!ok) return;
	for (int i = 1; i <= w; i++)
		ans[i] = s[p - i + 1];
}

int main() {
	rd(n, k), rds(s, n);
	for (int i = 1; i <= n; i++)
		s[n + n + 1 - i] = s[i];
	c = *min_element(s + 1, s + n + n + 1);
	for (int i = 1; i <= n + n; i++)
		a[i] = s[i] == c ? a[i - 1] + 1 : 0;
	l = *max_element(a + 1, a + n + n + 1);
	if (k >= 14 || ((n - 1) >> (k - 1)) + 1 <= l) {
		for (int i = 1; i <= n; i++) printc(c);
		return 0;
	}
	w = n - (l << (k - 1));
	ans[1] = 'z' + 1;
	for (int i = 1; i <= n; i++)
		if (a[n + i] == l) upd(n + i - l);
	for (int i = 1; i <= (l << (k - 1)); i++) printc(c);
	prints(ans, w);
	return 0;
}

Counting of Subarrays

题意

  • 给定一个正整数序列 a1…na_{1\dots n} 和一个正整数 ll,求 aa 有多少个非空子段 ai…ja_{i\dots j},满足存在正整数 kk 使得 ai…ja_{i \dots j} 属于等级 kk。
  • 对于一个正整数序列 aa 和正整数 k,lk,l,称 aa 属于等级 kk 当且仅当满足以下两个条件至少一个:
    • aa 的长度为 11 且唯一的元素为 kk。
    • 当 k≠1k \ne 1 时,可以将 aa 分成不少于 ll 个非空子段,使得每一段都属于等级 k−1k-1。
  • n≤2×105n \le 2 \times 10^5,2≤l≤n2 \le l \le n,ai≤109a_{i} \le 10^9。

题解

首先考虑如何判断一个序列 ss 是否合法(即存在正整数 kk 使得 ss 属于等级 kk)。

考虑将定义的过程反着推,相当于每次对于 ss 中最小的元素 xx,找到所有 xx 的连续段。设一个连续段的长度为 tt,若 t<lt < l 则判定为不合法,否则将这一段合并为 ⌊tl⌋\lfloor \frac tl \rfloor 个 x+1x + 1,直到 ss 中只剩下一种元素。注意这个元素一定是一开始 ss 中的最大值,同时我们不再合并这个元素。

设 ss 的最大值为 ww,此时分三种情况:

  • 若 ww 的数量为 11,这种情况当且仅当原序列的长度为 11,ss 属于等级 ww,合法。
  • 若 ww 的数量不少于 ll,ss 属于等级 w+1w + 1,合法。
  • 不合法。

考虑将这个过程搬到整个序列 aa 中。

首先对于第一种情况直接特判。

然后从小到大考虑 aa 中出现过的值 ww,当考虑到 ww 时,我们需要计算 aa 中所有最大值为 ww 的子段的中合法的数量,这个过程可以用一个单调栈维护。

即我们现在有一个序列 s=s0ws1w⋯wsms = s_0ws_1w \cdots ws_m,其中 sis_i 为一个所有元素都 <w<w 的序列,我们要求出这个序列中有多少个子段满足其属于等级 w+1w+1。

考虑对于一个序列 ss 维护以下几个信息(其中 ww 为 ss 中的最大值):

  • fs,if_{s,i} 表示 ss 中至多能合并出 ii 个 ww 的前缀个数。
  • gs,ig_{s,i} 表示 ss 中至多能合并出 ii 个 ww 的后缀个数。
  • hsh_{s} 表示 ss 至多能合并出的 ww 的个数,这个可以根据 f,gf,g 求出。

根据 fs,gs,hsf_s,g_s,h_s,我们可以简单地求出对于任意一个 w′>ww^\prime > w,ss 中至多能合并出 ii 个 w′w^\prime 的前/后缀个数,以及 ss 至多能合并出的 w′w^\prime 的个数。

假设我们现在对于 s=s0ws1w⋯wsms = s_0ws_1w \cdots ws_m 中的所有 sis_i 都求出了 fsi,gsi,hsif_{s_i},g_{s_i},h_{s_i},则我们首先可以求出 sis_i 中至多能合并出 ii 个 ww 的前/后缀个数,以及 ss 至多能合并出的 ww 的个数。然后使用 Two pointers 在 O(m+∑i=0mhsi)\mathcal O(m + \sum_{i=0}^m h_{s_i}) 的时间复杂度下求出 ss 的答案,以及 fs,gs,hsf_s,g_s,h_s。

接下来考虑一下时间复杂度,乍看下去可能时间复杂度非常高,但如果我们只记录不为 00 的 fs,gsf_s,g_s,状态总数是 O(nlog⁡n)\mathcal O(n \log n) 的。

这是因为对于 aa 中的一个元素 ww,它至多为最大值为 ww 的极长子段提供 11 的状态数,为最大值为 w+1w+1 的极长子段提供 1l\frac1l 的状态数,为最大值为 w+2w+2 的极长子段提供 1l2\frac1{l^2} 的状态数,因此总状态数实际上是 O(nlog⁡n)\mathcal O(n \log n) 的。

精细实现的话,总时间复杂度为 O(nlog⁡n)\mathcal O(n \log n)。

代码

const int N = 2e5 + 7;
int n, l, s[N], t, f[N], g[N], sf[N], sg[N];
ll ans;

inline void pop() {
	int k = 1, x = s[t];
	while (s[t-1] == x) --t, ++k;
	--t;
	if (k < l) {
		while (t) pop();
		return;
	}
	int w = k / l;
	for (int i = 1; i <= k; i++)
		sf[i] = sf[i-1] + f[i+t], sg[i] = sg[i-1] + g[i+t];
	for (int i = l; i <= k; i++)
		ans += 1ll * sf[i-l+1] * g[i+t];
	for (int i = 1; i <= w; i++)
		f[t+i] = sf[k-l*(w-i+1)+1] - sf[max(k-l*(w-i+2)+1,0)],
		g[t+i] = sg[min(l*(i+1)-1,k)] - sg[l*i-1];
	for (int i = l, s = 0; i <= w; i++)
		ans -= 1ll * (s += f[t+i-l+1]) * g[t+i];
	for (int i = 1; i <= w; i++)
		s[++t] = x + 1;
}

int main() {
	rd(n, l), ans = n;
	for (int i = 1, x; i <= n; i++) {
		rd(x);
		while (t && s[t] < x) pop();
		s[++t] = x, f[t] = g[t] = 1;
	}
	while (t) pop();
	print(ans);
	return 0;
}

标签:DP、Two Pointers、二分图、前缀和/差分、单调栈、组合计数、贪心

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…