「$\mathcal{Atcoder}$」$\mathcal{ARC101}$

闲来无事,写了一场比赛题,大家都认为题目质量很高,所以才去做的,不过题确实不错

——前言

\(\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\n", ans), 0;
}

\(\mathcal{Median\;of\;Medians}\)

首先定义中位数,将 \(n\) 个数排序后,如果有奇数个数,则中位数为 \(a[\frac{n}{2}]\),否则中位数为 \(a[\frac{n}{2}+1]\)

让你求出 \(\frac{n(n+1)}{2}\) 个区间中位数的中位数

首先考虑一下中位数的性质:

  • 如果 \(n\) 为奇数,中位数满足

\[num_{a[i]\geq a[mid]}=num_{a[i]\leq a[mid]} \]

  • 如果 \(n\) 为偶数,中位数满足

\[num_{a[i]\geq a[mid]}=num_{a[i]\leq a[mid]}-1 \]

不妨我们二分答案 \(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}(n\log n)\),所以是 \(\mathcal{O}(n\log^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\n", b[L - 1]), 0;
}

\(\mathcal{Ribbons\;on\;Tree}\)

首先可以很容易想到一个 \(\mathcal{O}(n^3)\)\(dp\),在子树归并的同时枚举这次匹配的点对数,然后转移

发现这个 \(dp\) 不太好进行优化,考虑换一个思路去做

题目中要求的是恰好所有边都被经过,即恰好有 \(0\) 条边不被经过,不妨考虑子集反演,钦定集合 \(S\in E\)\(F(S)\) 表示集合 \(S\) 中的边都不被经过的方案数,那我们最后要求的答案就变成了

\[\sum(-1)^{|S|}F(S) \]

考虑如何求 \(F(S)\),我们将这些边都断掉,会形成 \(|S|-1\) 个联通块,因为定义中并没有钦定剩下的边一定被经过,所以直接在同一个联通块里随便选两个点连就行了,要求的就是是这 \(|S|-1\) 个联通块中,每个联通块里的每两个点匹配且要完全匹配的方案数,显然这 \(|S|-1\) 个联通块是独立的,可以分开求

定义 \(g(n)\) 表示一个联通块里有 \(n\) 个点,两两随便匹配的方案数,因为点对是无序的,点对与点对之间也是无序的,所以我们直接钦定它有序,对每个点选个点匹配就行,直接可以得出式子

\[g(n)=[n\equiv 0\pmod2](n-1)(n-3)......3\times 1 \]

所以对于一个 \(F(S)\),其实就是它分成的若干个联通块的 \(g(n)\) 乘起来

接下来我们可以考虑继续在树上 \(dp\) 来解决这个问题

定义 \(f[i][j]\) 表示以 \(i\) 为根的子树,\(i\) 所在的联通块大小为 \(j\) 的方案数

然后子树归并,转移是决策 \(u\) 所在的联通块是否与 \(v\) 所在的联通块合并就行了

特别的,根据定义和转移,我们发现 \(f[i][0]\) 其实就是不与上面的联通块合并,即断掉了 \(fa[i]\)\(i\) 之间的边,因为断边集合 \(S\) 大小增加了 \(1\),需要乘上个 \(-1\),而且现在一个联通块考虑完了,同时在乘上一个 \(g(j)\) 即可,即

\[f[u][0]=-1\times \sum_{j=1}^{size[u]}f[u][j]\times g(j) \]

最后特殊考虑一下根节点,因为它没有 \(fa\),所以无边可断,不需要乘 \(-1\),最后答案即为 \(-1\times 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\n", 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_i\wedge y_{i-1}<y_i\),我们要求的就是不同的路径条数

具体操作时,先将左边和右边只有可能消失在一个出口的机器人去掉,然后求出剩下每个机器人的 \((x,y)\),按照 \(x_i\) 排序,因为是严格小于,所以 \(x_i\) 相同的按照 \(y_i\) 从大到小排序

最后做一遍最长上升子序列即可,用树状数组优化一下可以做到 \(\mathcal O({n\log 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\n", addmod (ans, 1)), 0;
}
posted @ 2021-06-20 11:12  Rubyonlу  阅读(100)  评论(0编辑  收藏  举报