AtCoder Grand Contest 028 题解
本文目录(18 节)
Two Abbreviations
答案显然要么为 要么为 ,判一下就好了。
const int N = 1e5 + 7;
int n, m, d;
char a[N], b[N];
int main() {
rd(n, m), d = __gcd(n, m);
rds(a, n), rds(b, m);
int s = n / d, t = m / d;
for (int i = 1, j = 1; i <= n; i += s, j += t)
if (a[i] != b[j]) return print(-1), 0;
print(1ll * n * m / d);
return 0;
}
Removing Blocks
考虑两个数 ,不妨设 ,则在删 时 有贡献当且仅当 比 先删,方案数为 。
对 做前缀和,枚举 计算贡献,时间复杂度 。
const int N = 1e5 + 7;
int n, a[N];
modint s[N], ans;
int main() {
rd(n), rda(a, n), init(n);
for (int i = 1; i <= n; i++) s[i] = p[n] * v[i] + s[i-1];
for (int i = 1; i <= n; i++)
ans += a[i] * (s[i] + s[n-i+1] - s[1]);
print(ans);
return 0;
}
Min Cost Cycle
题意
- 有一张 个点的有向完全图。
- 每个点 有两个权值 ,对于两个点 ,边 的长度为 。
- 考虑图中经过每个点恰好一次的有向环,求出其中的最短路径长。
- 。
题解
换个角度考虑,相当于要从 个数中选择 个数,使其可以连成一个环,环中每条边前一个 和后一个 中选择恰好一个。
可行的方案有三种:
- 全选 ,合法性显然,不证。
- 全选 ,合法性显然,不证。
- 至少有一个 满足 都被选了。
第三种的合法性证明如下:
显然每个数只有四种状态,我们将其看成 四种,那么条件就是至少有一个 。
因为 的总数为 ,所以 和 的数量相等,将它们交替放()。
将所有 和 首尾相连变成一个 一个 ,然后随便插到 的环中合法的位置即可。
而除了这三种方案,剩下的情况的不合法性也很显然。
于是用 set 维护前 小的数,然后枚举 都被选的 ,可以 得到答案。
总时间复杂度 。
代码
const int N = 1e5 + 7;
int n, a[N], b[N];
ll sa, sb, sum, ans;
set<pi> s;
bool v[N], w[N];
int main() {
rd(n);
for (int i = 1; i <= n; i++)
rd(a[i], b[i]), sa += a[i], sb += b[i],
s.insert(mp(a[i], i)), s.insert(mp(b[i], -i));
ans = min(sa, sb), sum = sa + sb;
while ((int)s.size() > n)
sum -= (--s.end()) -> fi, s.erase(--s.end());
for (pi o : s)
if (o.se > 0) v[o.se] = 1;
else w[-o.se] = 1;
for (int i = 1; i <= n; i++)
if (v[i]) {
if (w[i]) ans = min(ans, sum);
else {
auto it = --s.end();
while (it -> se == i) --it;
ans = min(ans, sum + b[i] - (it -> fi));
}
} else {
if (w[i]) {
auto it = --s.end();
while (it -> se == -i) --it;
ans = min(ans, sum + a[i] - (it -> fi));
} else {
auto it1 = --s.end(), it2 = --(--s.end());
ans = min(ans, sum + a[i] + b[i] - (it1 -> fi) - (it2 -> fi));
}
}
print(ans);
return 0;
}
Chords
题意
- 一个圆上均匀分布着 个点。
- 你要把这些点分成 对,每对的两点之间连接一条线。
- 其中有 对点已经确定了,你要求出每种方案通过连线连通的块数。
- ,答案对 取模。
题解
首先断环成链,圆上的一个连通块在链上等价于一个区间,满足整个连通块都在这个区间内,且区间的两端点都属于这个连通块,但是注意并不要求整个区间都属于这个连通块,因为其中可能还有小连通块。
令 表示 为上述连通块对应的区间的方案数。
如果任意连边,方案数为 ,其中 表示 内还没确定连边的点数, 表示将 个点两两连边的方案数,有 ,特别地 。
但是还要求 都属于这个连通块,考虑容斥,枚举跟 在同一个连通块的右端点 ,有: 最终的答案为 。
代码
const int N = 607;
int n, k, p[N], c[N];
modint f[N][N], g[N], ans;
bool v[N][N];
inline int C(int i, int j) {
return j - i + 1 - c[j] + c[i-1];
}
inline modint F(int i, int j) {
if (v[i][j]) return f[i][j];
v[i][j] = 1;
if (((j - i + 1) & 1) || (C(i, j) & 1)) return 0;
for (int k = i; k <= j; k++)
if (p[k] && (p[k] < i || p[k] > j)) return 0;
f[i][j] = g[C(i,j)];
for (int k = i; k < j; k++)
f[i][j] -= F(i, k) * g[C(k+1,j)];
return f[i][j];
}
int main() {
rd(n, k);
for (int i = 1, x, y; i <= k; i++)
rd(x, y), p[x] = y, p[y] = x, c[x] = 1, c[y] = 1;
for (int i = 1; i <= n << 1; i++) c[i] += c[i-1];
g[0] = 1;
for (int i = 2; i <= n << 1; i++)
g[i] = g[i-2] * (i - 1);
for (int i = 1; i <= n << 1; i++)
for (int j = i; j <= n << 1; j++)
ans += F(i, j) * g[2*(n-k)-C(i,j)];
print(ans);
return 0;
}
High Elements
题意
- 给定一个 的排列 。
- 求出字典序最小的满足下列条件的 串 ,或判断不存在:
- 一开始有两个空序列 。
- 依次考虑 ,若 为
0则将 加入 末尾,否则加入 末尾。 - 中为前缀 的位置数量相同。
- 。
题解
看到字典序显然贪心,现在前 位已经确定,考虑第 位,先假设为 0,如果不合法则改成 1。接下来的问题就是如何判断是否合法。
首先可以证明,如果存在合法的 ,必然可以使得 两个数列中,有一个数列的前缀 都是 中的前缀 。这是因为如果不是,交换两个不是 中前缀 的前缀 可以把这两个前缀 都消掉。
不妨假设 中的前缀 都由 的前缀 构成,令 当前的前缀 个数为 , 中前缀 个数为 。设 中将会有 个前缀 也是 的前缀 ,则 中会有 个。再设 中不是 的前缀 的前缀 个数为 ,我们能得到等式: 即: 其中右边是常量,设为 。
由于在 或 时可以从 的方案构造出减少 的方案,因此若满足存在 且 ,就存在合法解。
考虑 ,相当于我们把 中的前缀 的权值设为 ,其它位置的权值设为 后的带权最长上升子序列长度。
于是用权值线段树维护单点修改和区间求 ,求出 从 开始长度为偶/奇数的带权最长上升子序列的长度,判断时用类似的操作求出 在偶/奇数下的最大值即可。
由于我们的贪心策略默认了有解,所以最后如果 则说明无解。
时间复杂度 。
代码
const int N = 2e5 + 7, inf = 1e9;
int n, p[N], c[N], cx, cy;
bool v[N];
char s[N];
struct SGT {
struct T {
int l, r, x;
} t[N<<2|1];
inline void update(int p) {
t[p].x = max(t[ls].x, t[rs].x);
}
void build(int p, int l, int r, int x) {
t[p].l = l, t[p].r = r;
if (l == r) return t[p].x = x, void();
build(ls, l, md, x), build(rs, md + 1, r, x);
update(p);
}
void add(int p, int x, int k) {
if (t[p].l == t[p].r) return t[p].x = k, void();
if (x <= md) add(ls, x, k);
if (x > md) add(rs, x, k);
update(p);
}
int ask(int p, int l, int r) {
if (t[p].l >= l && t[p].r <= r) return t[p].x;
int ret = -inf;
if (l <= md) ret = max(ret, ask(ls, l, r));
if (r > md) ret = max(ret, ask(rs, l, r));
return ret;
}
} t[2];
inline bool pd(int x, int k) {
return k < 0 ? 0 : t[k&1].ask(1, x, n) >= k;
}
int main() {
rd(n), rda(p, n);
for (int i = 1, x = 0; i <= n; i++)
if (p[i] > x) x = p[i];
else v[i] = 1;
t[0].build(1, 1, n, 0), t[1].build(1, 1, n, -inf);
for (int i = n; i; i--) {
int x0 = t[0].ask(1, p[i], n), x1 = t[1].ask(1, p[i], n);
t[0].add(1, p[i], (v[i] ? x1 + 1 : x0 + 2));
t[1].add(1, p[i], (v[i] ? x0 + 1 : x1 + 2));
c[i] = c[i+1] + !v[i];
}
for (int i = 1, x = 0, y = 0; i <= n; i++) {
t[0].add(1, p[i], 0), t[1].add(1, p[i], -inf);
if (pd(y, c[i+1] + cx - cy + (p[i] > x)) || pd(max(x, p[i]), c[i+1] + cy - cx - (p[i] > x)))
s[i] = '0', cx += p[i] > x, x = max(x, p[i]);
else s[i] = '1', cy += p[i] > y, y = max(y, p[i]);
}
if (cx == cy) prints(s, n);
else print(-1);
return 0;
}
Reachable Cells
题意
- 给定一个 的网格,每个位置上要么是障碍,要么写有一个 的数。
- 我们称位置 可以到达位置 当且仅当:
- 。
- 都不是障碍。
- 存在一条从 到 的路径,满足只经过不是障碍的格子,同时每次只向下或者向右走到相邻的格子。
- 对于二元组 ,若 可以到达 ,则 合法且权值为 上写的数的乘积。
- 要求所有合法二元组的权值之和。
- 。
题解
考虑从大到小枚举位置 求出能到达的点的权值和 。
如果 和 中至多一个不是障碍则很简单,否则直接加 和 会算重,要减掉两个点都可达的。
设 表示 能到达的点在第 行列数的最小值和最大值,显然对于 和 都能到达的行 有 ,。
注意到如果第 行有 和 都能到达的点,那么必然包括 ,且 和 都能到达的点恰好为若干个 能到达的点的并集,使这些点集不交。
于是可以 计算,常数很小可以通过,注意滚动数组防止炸空间。
代码
const int N = 1.5e3 + 7;
int n, s[N][N], p[N][N], l[2][N][N], r[2][N][N];
char a[N][N];
ll ans;
int main() {
rd(n);
for (int i = 1; i <= n; i++) rds(a[i], n);
for (int i = n, o = 0; i; i--, o ^= 1)
for (int j = n; j > 0; j--)
if (a[i][j] != '#') {
a[i][j] -= '0';
s[i][j] = s[i][j+1] + s[i+1][j] + a[i][j];
p[i][j] = max(max(p[i][j+1], p[i+1][j]), i);
l[o][j][i] = j;
for (int k = i + 1; k <= p[i+1][j]; k++)
l[o][j][k] = l[o^1][j][k];
for (int k = max(p[i+1][j], i) + 1; k <= p[i][j+1]; k++)
l[o][j][k] = l[o][j+1][k];
r[o][j][i] = j;
for (int k = i; k <= p[i][j+1]; k++)
r[o][j][k] = r[o][j+1][k];
for (int k = max(p[i][j+1], i) + 1; k <= p[i+1][j]; k++)
r[o][j][k] = r[o^1][j][k];
for (int k = i + 1; k <= min(p[i][j+1], p[i+1][j]); k++)
if (r[o^1][j][k] >= l[o][j+1][k])
s[i][j] -= s[k][l[o][j+1][k]],
k = p[k][l[o][j+1][k]];
ans += 1ll * a[i][j] * (s[i][j] - a[i][j]);
}
print(ans);
return 0;
}
评论
暂无已公开评论。