「Atcoder」ARC101
闲来无事,写了一场比赛题,大家都认为题目质量很高,所以才去做的,不过题确实不错
——前言
Candles#
算是本场的签到题
我们可以先找到 0 的位置
容易发现,走的路径一定是一段包含 0 点的区间
一段区间的答案是:向左走的距离 disl 加上向右走的距离 disr 加上 min(disl,disr)
显然区间的个数是 O(n) 的,对这 n 个区间答案取个 min 就是最后的答案,复杂度是 O(n) 的
代码
复制#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
int n, m, L, R = 1, ans = INF, a[maxn], b[maxn];
int main () {
n = read(), m = read();
for (register int i = 1; i <= n; i ++) a[i] = read(), b[i] = abs (a[i]);
for (register int i = 1; i <= n; i ++)
if (a[i] < 0 && a[i + 1] >= 0) L = i, R = i + 1;
if (L >= m) {
register int l = L - m + 1, r = R - 1;
while (r <= n && l <= R) {
if (a[l] < 0 && a[r] > 0) ans = min (ans, b[l] + b[r] + min (b[l], b[r]));
else if (a[l] < 0 && a[r] <= 0) ans = min (ans, b[l]);
else if (a[l] >= 0 && a[r] >= 0) ans = min (ans, b[r]);
l ++, r ++;
}
} else {
register int l = 1, r = R + m - L - 1;
while (r <= n && l <= R) {
if (a[l] < 0 && a[r] > 0) ans = min (ans, b[l] + b[r] + min (b[l], b[r]));
else if (a[l] < 0 && a[r] <= 0) ans = min (ans, b[l]);
else if (a[l] >= 0 && a[r] >= 0) ans = min (ans, b[r]);
l ++, r ++;
}
}
return printf ("%d\n", ans), 0;
}
MedianofMedians#
首先定义中位数,将 n 个数排序后,如果有奇数个数,则中位数为 a[n2],否则中位数为 a[n2+1]
让你求出 n(n+1)2 个区间中位数的中位数
首先考虑一下中位数的性质:
- 如果 n 为奇数,中位数满足
- 如果 n 为偶数,中位数满足
不妨我们二分答案 x,然后用中位数的性质 check 答案是否正确,现在问题是如何求出有多少个区间的中位数大于等于 x,有多少个区间的中位数小于等于 x
我们将 ≥x 的数置为 1,<x 的数置为 −1
容易发现,一个区间的和 ≥0,说明这个区间的中位数 ≥x,如果这个和 <0,说明这个区间的中位数 ≤x
根据求区间和 sum[r]−sum[l−1] 的式子,我们可以对于每个右端点求出左边有多少个点的 sum[l]≤sum[r],同时也可以求出 sum[l]>sum[r] 的个数,这个用树状数组维护一下就行了
最后复杂度是二分内套一个 O(nlogn),所以是 O(nlog2n) 的
代码
复制#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn = 2e5 + 50, INF = 0x3f3f3f3f, base = 1e5;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
int n, typ, a[maxn], b[maxn], c[maxn], tree[maxn];
inline void Insert0 (register int x) {
for (; x <= maxn; x += x & -x) tree[x] ++;
}
inline int Query0 (register int x, register int ans = 0) {
for (; x; x -= x & -x) ans += tree[x];
return ans;
}
inline void Insert1 (register int x) {
for (; x; x -= x & -x) tree[x] ++;
}
inline int Query1 (register int x, register int ans = 0) {
for (; x <= maxn; x += x & -x) ans += tree[x];
return ans;
}
inline bool Check (register int x, register ll num0 = 0, register ll num1 = 0) {
memset (tree, 0, sizeof tree), Insert0 (0 + base);
for (register int i = 1; i <= n; i ++) c[i] = a[i] < x ? -1 : 1;
for (register int i = 1, res = 0; i <= n; i ++)
res += c[i], num0 += Query0 (res + base), Insert0 (res + base);
memset (tree, 0, sizeof tree), Insert1 (0 + base);
for (register int i = 1, res = 0; i <= n; i ++)
res += c[i], num1 += Query1 (res + base + 1), Insert1 (res + base);
return num0 >= num1 - typ;
}
int main () {
n = read(), typ = 1ll * n * (n + 1) / 2 % 2 == 0;
for (register int i = 1; i <= n; i ++) a[i] = b[i] = read();
sort (b + 1, b + n + 1);
register int L = 1, R = n;
while (L <= R) {
register int mid = (L + R) >> 1;
if (Check (b[mid])) L = mid + 1;
else R = mid - 1;
}
return printf ("%d\n", b[L - 1]), 0;
}
RibbonsonTree#
首先可以很容易想到一个 O(n3) 的 dp,在子树归并的同时枚举这次匹配的点对数,然后转移
发现这个 dp 不太好进行优化,考虑换一个思路去做
题目中要求的是恰好所有边都被经过,即恰好有 0 条边不被经过,不妨考虑子集反演,钦定集合 S∈E,F(S) 表示集合 S 中的边都不被经过的方案数,那我们最后要求的答案就变成了
考虑如何求 F(S),我们将这些边都断掉,会形成 |S|−1 个联通块,因为定义中并没有钦定剩下的边一定被经过,所以直接在同一个联通块里随便选两个点连就行了,要求的就是是这 |S|−1 个联通块中,每个联通块里的每两个点匹配且要完全匹配的方案数,显然这 |S|−1 个联通块是独立的,可以分开求
定义 g(n) 表示一个联通块里有 n 个点,两两随便匹配的方案数,因为点对是无序的,点对与点对之间也是无序的,所以我们直接钦定它有序,对每个点选个点匹配就行,直接可以得出式子
所以对于一个 F(S),其实就是它分成的若干个联通块的 g(n) 乘起来
接下来我们可以考虑继续在树上 dp 来解决这个问题
定义 f[i][j] 表示以 i 为根的子树,i 所在的联通块大小为 j 的方案数
然后子树归并,转移是决策 u 所在的联通块是否与 v 所在的联通块合并就行了
特别的,根据定义和转移,我们发现 f[i][0] 其实就是不与上面的联通块合并,即断掉了 fa[i] 与 i 之间的边,因为断边集合 S 大小增加了 1,需要乘上个 −1,而且现在一个联通块考虑完了,同时在乘上一个 g(j) 即可,即
最后特殊考虑一下根节点,因为它没有 fa,所以无边可断,不需要乘 −1,最后答案即为 −1×f[1][0]
因为只剩下了子树归并,所以复杂度是 O(n2) 的
代码
复制#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn = 5e3 + 50, INF = 0x3f3f3f3f, mod = 1e9 + 7;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
inline int addmod (register int a, register int b) {
return a += b, a >= mod ? a - mod : a;
}
inline ll mulmod (register ll a, register int b) {
return a *= b, a >= mod ? a % mod : a;
}
int n;
struct Edge {
int to, next;
} e[maxn << 1];
int tot, head[maxn];
inline void Add (register int u, register int v) {
e[++ tot].to = v;
e[tot].next = head[u];
head[u] = tot;
}
int siz[maxn], f[maxn][maxn], g[maxn], p[maxn];
inline void Init () {
g[0] = 1;
for (register int i = 2; i <= n; i += 2)
g[i] = mulmod (g[i - 2], i - 1);
}
inline void DFS (register int u, register int fa) {
siz[u] = 1, f[u][1] = 1;
for (register int i = head[u]; i; i = e[i].next) {
register int v = e[i].to;
if (v == fa) continue;
DFS (v, u);
for (register int j = 1; j <= siz[u] + siz[v]; j ++) p[j] = 0;
for (register int j = 1; j <= siz[u]; j ++)
for (register int k = 0; k <= siz[v]; k ++)
p[j + k] = addmod (p[j + k], mulmod (f[u][j], f[v][k]));
for (register int j = 1; j <= siz[u] + siz[v]; j ++) f[u][j] = p[j];
siz[u] += siz[v];
}
for (register int j = 1; j <= siz[u]; j ++)
f[u][0] = addmod (f[u][0], mulmod (f[u][j], g[j]));
f[u][0] = mod - f[u][0];
}
int main () {
n = read(), Init ();
for (register int i = 1, u, v; i < n; i ++)
u = read(), v = read(), Add (u, v), Add (v, u);
DFS (1, 0), printf ("%d\n", mod - f[1][0]);
return 0;
}
RobotsandExits#
容易发现,这 m 个出口会把 n 个机器人分成若干段
对于某一段的这些机器人,要么从左边最近的出口消失,要么从右边最近的出口消失,从而我们可以得到每个机器人到左边的距离 x,到右边的距离 y,形成了若干个这样的点对 (x,y)
我们考虑两个机器人 (x0,y0),(x1,y1),如果 x0<x1,那么只要机器人 1 从左边消失了,那么机器人 0 也一定是从左边消失的
把这些点对放到坐标系上,问题其实就是每次将 x 轴往上移,或者将 y 轴往右移,先被 x 轴覆盖掉的为白色,先被 y 轴覆盖掉的为黑色
我们可以将覆盖掉的点按顺序转化成一条路径,路径上的点满足 xi−1<xi∧yi−1<yi,我们要求的就是不同的路径条数
具体操作时,先将左边和右边只有可能消失在一个出口的机器人去掉,然后求出剩下每个机器人的 (x,y),按照 xi 排序,因为是严格小于,所以 xi 相同的按照 yi 从大到小排序
最后做一遍最长上升子序列即可,用树状数组优化一下可以做到 O(nlogn) 的复杂度
代码
复制#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f, mod = 1e9 + 7;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
inline int addmod (register int a, register int b) {
return a += b, a >= mod ? a - mod : a;
}
int n, m, num, ans, lenx, leny, x[maxn], y[maxn], bx[maxn], by[maxn], tree[maxn];
struct Node {
int x, y;
inline friend bool operator < (register const Node &a, register const Node &b) { return a.x == b.x ? a.y > b.y : a.x < b.x; }
} a[maxn];
inline void Insert (register int x, register int val) {
for (; x <= leny; x += x & -x) tree[x] = addmod (tree[x], val);
}
inline int Query (register int x, register int ans = 0) {
for (; x; x -= x & -x) ans = addmod (ans, tree[x]);
return ans;
}
int main () {
n = read(), m = read();
for (register int i = 1; i <= n; i ++) x[i] = read();
for (register int i = 1; i <= m; i ++) y[i] = read();
for (register int i = 1, res; i <= n; i ++) {
res = upper_bound (y + 1, y + m + 1, x[i]) - y;
if (res - 1 >= 1 && res <= m) num ++, a[num].x = bx[++ lenx] = x[i] - y[res - 1], a[num].y = by[++ leny] = y[res] - x[i];
}
sort (bx + 1, bx + lenx + 1), lenx = unique (bx + 1, bx + lenx + 1) - bx - 1;
sort (by + 1, by + leny + 1), leny = unique (by + 1, by + leny + 1) - by - 1;
for (register int i = 1; i <= num; i ++)
a[i].x = lower_bound (bx + 1, bx + lenx + 1, a[i].x) - bx, a[i].y = lower_bound (by + 1, by + leny + 1, a[i].y) - by;
sort (a + 1, a + num + 1);
for (register int i = 1, res; i <= num; i ++) {
if (a[i].x == a[i - 1].x && a[i].y == a[i - 1].y) continue;
res = Query (a[i].y) + 1, Insert (a[i].y + 1, res), ans = addmod (ans, res);
}
return printf ("%d\n", addmod (ans, 1)), 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· MySQL下200GB大表备份,利用传输表空间解决停服发版表备份问题
· 记一次 .NET某固高运动卡测试 卡慢分析
· 微服务架构学习与思考:微服务拆分的原则
· 记一次 .NET某云HIS系统 CPU爆高分析
· 如果单表数据量大,只能考虑分库分表吗?
· 7 个最近很火的开源项目「GitHub 热点速览」
· DeepSeekV3:写代码很强了
· 记一次 .NET某固高运动卡测试 卡慢分析
· Visual Studio 2022 v17.13新版发布:强化稳定性和安全,助力 .NET 开发提
· MySQL下200GB大表备份,利用传输表空间解决停服发版表备份问题