闲来无事,写了一场比赛题,大家都认为题目质量很高,所以才去做的,不过题确实不错
——前言
(mathcal{Candles})
算是本场的签到题
我们可以先找到 (0) 的位置
容易发现,走的路径一定是一段包含 (0) 点的区间
一段区间的答案是:向左走的距离 (dis_l) 加上向右走的距离 (dis_r) 加上 (min(dis_l,dis_r))
显然区间的个数是 (mathcal{O}(n)) 的,对这 (n) 个区间答案取个 (min) 就是最后的答案,复杂度是 (mathcal{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
", ans), 0;
}
(mathcal{Median;of;Medians})
首先定义中位数,将 (n) 个数排序后,如果有奇数个数,则中位数为 (a[frac{n}{2}]),否则中位数为 (a[frac{n}{2}+1])
让你求出 (frac{n(n+1)}{2}) 个区间中位数的中位数
首先考虑一下中位数的性质:
- 如果 (n) 为奇数,中位数满足
- 如果 (n) 为偶数,中位数满足
不妨我们二分答案 (x),然后用中位数的性质 (check) 答案是否正确,现在问题是如何求出有多少个区间的中位数大于等于 (x),有多少个区间的中位数小于等于 (x)
我们将 (geq x) 的数置为 (1),(< x) 的数置为 (-1)
容易发现,一个区间的和 (geq 0),说明这个区间的中位数 (geq x),如果这个和 (< 0),说明这个区间的中位数 (leq x)
根据求区间和 (sum[r]-sum[l-1]) 的式子,我们可以对于每个右端点求出左边有多少个点的 (sum[l] leq sum[r]),同时也可以求出 (sum[l] > sum[r]) 的个数,这个用树状数组维护一下就行了
最后复杂度是二分内套一个 (mathcal{O}(nlog n)),所以是 (mathcal{O}(nlog^2 n)) 的
代码
#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
", b[L - 1]), 0;
}
(mathcal{Ribbons;on;Tree})
首先可以很容易想到一个 (mathcal{O}(n^3)) 的 (dp),在子树归并的同时枚举这次匹配的点对数,然后转移
发现这个 (dp) 不太好进行优化,考虑换一个思路去做
题目中要求的是恰好所有边都被经过,即恰好有 (0) 条边不被经过,不妨考虑子集反演,钦定集合 (Sin 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 imes f[1][0])
因为只剩下了子树归并,所以复杂度是 (mathcal{O}(n^2)) 的
代码
#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
", mod - f[1][0]);
return 0;
}
(mathcal{Robots;and;Exits})
容易发现,这 (m) 个出口会把 (n) 个机器人分成若干段
对于某一段的这些机器人,要么从左边最近的出口消失,要么从右边最近的出口消失,从而我们可以得到每个机器人到左边的距离 (x),到右边的距离 (y),形成了若干个这样的点对 ((x,y))
我们考虑两个机器人 ((x_0,y_0),(x_1,y_1)),如果 (x_0<x_1),那么只要机器人 (1) 从左边消失了,那么机器人 (0) 也一定是从左边消失的
把这些点对放到坐标系上,问题其实就是每次将 (x) 轴往上移,或者将 (y) 轴往右移,先被 (x) 轴覆盖掉的为白色,先被 (y) 轴覆盖掉的为黑色
我们可以将覆盖掉的点按顺序转化成一条路径,路径上的点满足 (x_{i-1}<x_iwedge y_{i-1}<y_i),我们要求的就是不同的路径条数
具体操作时,先将左边和右边只有可能消失在一个出口的机器人去掉,然后求出剩下每个机器人的 ((x,y)),按照 (x_i) 排序,因为是严格小于,所以 (x_i) 相同的按照 (y_i) 从大到小排序
最后做一遍最长上升子序列即可,用树状数组优化一下可以做到 (mathcal O({nlog n})) 的复杂度
代码
#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
", addmod (ans, 1)), 0;
}