AtCoder Grand Contest 037 题解
本文目录(15 节)
Dividing a String
可以证明划分出来的段长度不超过 ,于是 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
最小值直接贪心,方案数把每次的选择数量乘起来,最后乘上 。
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
倒着考虑,如果一个位置比左右两边都大,那么这个位置上的数必然是通过一次对这个位置的操作得到的。
于是可以每次从最大值开始不断往下减(除),中途判一下无解的情况,时间复杂度大概是 。
#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
题意
- 给定一个 的矩阵,每个位置上的数 ,且每个数恰好出现一次。
- 你可以进行三次操作:
- 将每一行重排。
- 将每一列重排。
- 再将每一行重排。
- 要求最终矩阵被还原为有序矩阵。
- 。
题解
倒着考虑,第三步会将同一行的打乱,因此同一行的顺序不重要,于是可以将每个数的行数看成它们的颜色。
第二步会将同一列的打乱,因此第二步之前每一列应该恰好每种颜色各有一个。
于是问题变成了,在一个矩阵中有 种颜色,每种颜色恰好 个,要通过重排每一行,实现每一列恰好每种颜色各一个。
考虑二分图匹配模型,左边为 行,右边为 种颜色,对于每一行的每种颜色连边,
然后找 次最大匹配,将第 的匹配作为第 列。根据 Hall 定理,每次匹配必然是完美匹配。
使用匈牙利算法时间复杂度为 。
代码
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
题意
- 给定一个长度为 的字符串 。
- 你可以进行如下操作 次:
- 将 变成 的一个长度为 的子串。
- 要求最终 的字典序最小。
- ,。
题解
另 中最小的字符为 ,设 中最长连续的 的长度为 ,则最终的 内我们至少可以构造出开头连续 个 的串。
若 是取到 ,则意味着后面还有一些字符要确定,可以发现后面的字符一定是 的一个后缀,由于数据范围很小,直接暴力找最小后缀即可,时间复杂度 。
代码
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
题意
- 给定一个正整数序列 和一个正整数 ,求 有多少个非空子段 ,满足存在正整数 使得 属于等级 。
- 对于一个正整数序列 和正整数 ,称 属于等级 当且仅当满足以下两个条件至少一个:
- 的长度为 且唯一的元素为 。
- 当 时,可以将 分成不少于 个非空子段,使得每一段都属于等级 。
- ,,。
题解
首先考虑如何判断一个序列 是否合法(即存在正整数 使得 属于等级 )。
考虑将定义的过程反着推,相当于每次对于 中最小的元素 ,找到所有 的连续段。设一个连续段的长度为 ,若 则判定为不合法,否则将这一段合并为 个 ,直到 中只剩下一种元素。注意这个元素一定是一开始 中的最大值,同时我们不再合并这个元素。
设 的最大值为 ,此时分三种情况:
- 若 的数量为 ,这种情况当且仅当原序列的长度为 , 属于等级 ,合法。
- 若 的数量不少于 , 属于等级 ,合法。
- 不合法。
考虑将这个过程搬到整个序列 中。
首先对于第一种情况直接特判。
然后从小到大考虑 中出现过的值 ,当考虑到 时,我们需要计算 中所有最大值为 的子段的中合法的数量,这个过程可以用一个单调栈维护。
即我们现在有一个序列 ,其中 为一个所有元素都 的序列,我们要求出这个序列中有多少个子段满足其属于等级 。
考虑对于一个序列 维护以下几个信息(其中 为 中的最大值):
- 表示 中至多能合并出 个 的前缀个数。
- 表示 中至多能合并出 个 的后缀个数。
- 表示 至多能合并出的 的个数,这个可以根据 求出。
根据 ,我们可以简单地求出对于任意一个 , 中至多能合并出 个 的前/后缀个数,以及 至多能合并出的 的个数。
假设我们现在对于 中的所有 都求出了 ,则我们首先可以求出 中至多能合并出 个 的前/后缀个数,以及 至多能合并出的 的个数。然后使用 Two pointers 在 的时间复杂度下求出 的答案,以及 。
接下来考虑一下时间复杂度,乍看下去可能时间复杂度非常高,但如果我们只记录不为 的 ,状态总数是 的。
这是因为对于 中的一个元素 ,它至多为最大值为 的极长子段提供 的状态数,为最大值为 的极长子段提供 的状态数,为最大值为 的极长子段提供 的状态数,因此总状态数实际上是 的。
精细实现的话,总时间复杂度为 。
代码
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;
}
评论
暂无已公开评论。