← 全部文章

AtCoder Grand Contest 021 题解

本文目录(12 节)
  1. Digit Sum 2
  2. Holes
  3. Tiling
  4. Reversed LCS
  5. Ball Eat Chameleons
  6. 题意
  7. 题解
  8. 代码
  9. Trinity
  10. 题意
  11. 题解
  12. 代码

AtCoder Grand Contest 021

Digit Sum 2

枚举答案与 nn 从高到低第一个不同的位。

int main() {
	string s;
	cin >> s;
	int ans = 0;
	for (char c : s) ans += c - '0';
	for (ui i = 0; i < s.length(); i++) {
		if (s[i] == '0') continue;
		int now = s[i] - '0' - 1;
		for (ui j = 0; j < i; j++)
			now += s[j] - '0';
		now += 9 * (s.length() - i - 1);
		ans = max(ans, now);
	}
	print(ans);
	return 0;
}

Holes

先求出凸包,对于凸包内的点答案为 00,凸包上的点的答案为,凸包上以这个点为顶点的外角在圆中所占的比例。

const ld eps = 1e-10L, PI = acos(-1);

struct P {
	ll x, y;
	int i;
	inline P() {}
	inline P(ll x, ll y, int i) : x(x), y(y), i(i) {}
	inline P &operator += (P o) { return x += o.x, y += o.y, *this; }
	inline P &operator -= (P o) { return x -= o.x, y -= o.y, *this; }
	inline friend P operator + (P a, P b) { return a += b; }
	inline friend P operator - (P a, P b) { return a -= b; }
	inline friend bool operator < (P a, P b) { return a.x == b.x ? a.y < b.y : a.x < b.x; }
	inline friend ll operator * (P a, P b) { return a.x * b.x + a.y * b.y; }
	inline friend ll operator % (P a, P b) { return a.x * b.y - a.y * b.x; }
	inline friend ld operator ^ (P a, P b) { return a * b / a.l() / b.l(); }
	inline ld l() { return sqrt(*this * *this); }
};

int n;

inline vector<P> convex_hull(vector<P> a) {
	sort(a.begin(), a.end());
	vector<P> p;
	for (int i = 0; i < n; i++) {
		while (p.size() > 1u &&
			(p.back() - p[p.size()-2]) % (a[i] - p[p.size()-2]) <= 0)
				p.pop_back();
		p.pb(a[i]);
	}
	ui m = p.size();
	for (int i = n - 2; ~i; i--) {
		while (p.size() > m &&
			(p.back() - p[p.size()-2]) % (a[i] - p[p.size()-2]) <= 0)
				p.pop_back();
		p.pb(a[i]);
	}
	p.pop_back();
	return p;
}

inline ld calc(P x, P y, P z) {
	return (PI - acos((x - y) ^ (z - y))) / (2 * PI);
}

int main() {
	rd(n);
	vector<P> a;
	for (int i = 0, x, y; i < n; i++) rd(x, y), a.pb(P(x, y, i));
	a = convex_hull(a);
	vector<ld> ans(n);
	int m = a.size();
	if (m == 2) for (P o : a) ans[o.i] = 0.5;
	else for (int i = 0; i < m; i++)
		ans[a[(i+1)%m].i] = calc(a[i], a[(i+1)%m], a[(i+2)%m]);
	for (int i = 0; i < n; i++) printf("%.20Lf\n", ans[i]);
	return 0;
}

Tiling

莫名其妙的一道分类讨论贪心构造题,AGC 少有的垃圾题嗷。

const int N = 1e3 + 7;
int n, m, a, b;
char c[N][N];

#define f(p, q) c[p][q] = '^', c[p+1][q] = 'v', --b;
#define g(p, q) c[p][q] = '<', c[p][q+1] = '>', --a;

int main() {
	rd(n, m), rd(a, b);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) c[i][j] = '.';
	if (m & 1) for (int i = 1; i < n && b; i += 2) f(i, m);
	if (n > 1 && m > 2 && n & 1 && b & 1) f(n - 1, m - 2);
	for (int i = 1; i < n; i++)
		for (int j = 1; j <= m - (m & 1) && b; j++)
			if (c[i][j] == '.' && c[i + 1][j] == '.') f(i, j);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j < m && a; j++)
			if (c[i][j] == '.' && c[i][j + 1] == '.') g(i, j);
	if (a + b) return prints("NO"), 0;
	prints("YES");
	for (int i = 1; i <= n; i++) prints(c[i] + 1);
	return 0;
}

Reversed LCS

一个结论是:ss 与 srs^r 的最长公共子序列,等于 ss 的最长回文子序列。

于是 O(n3)\mathcal O(n^3) 的区间 DP 即可,记搜的写法非常简洁。

const int N = 307, inf = 1e9;
int n, k, f[N][N][N];
char s[N];
bool v[N][N][N];

int F(int l, int r, int k) {
	if (k < 0) return -inf;
	if (r - l < 1) return r - l + 1;
	if (v[l][r][k]) return f[l][r][k];
	v[l][r][k] = 1;
	return f[l][r][k] = max(max(F(l + 1, r, k), F(l, r - 1, k)), F(l + 1, r - 1, k - (s[l] != s[r])) + 2);
}

int main() {
	rds(s, n), rd(k), print(F(1, n, k));
	return 0;
}

Ball Eat Chameleons

题意

  • 有 nn 只变色龙,初始为蓝色。
  • 有 kk 个球,每个球为红色或者蓝色。
  • 按顺序将 kk 个球随机给某只变色龙吃,此时它吃下哪种颜色的球多就会变成那种颜色,如果一样多则颜色不变。
  • 求有多少种方案使得最终有可能让所有变色龙都变成红色。
  • n,k≤5×105n,k \le 5 \times 10^5,答案对 998244353998244353 取模。

题解

考虑有 rr 个红球 bb 个蓝球,有 r+b=kr + b = k。

考虑变色龙最终为红色的两种情况:

  • 它吃的红球比蓝球多。
  • 它吃的红球和蓝球一样多且不为 00 个,但最后吃的是蓝球。

若 r<br < b 显然无解。

若 r≥b+nr \ge b + n,则必然有解。

否则 b≤r<b+nb \le r < b + n,最优的情况为有 r−br-b 只变色龙多吃一个红球,其它 n−(r−b)n-(r-b) 只变色龙都会先吃一个红球再吃一个蓝球。

即若将 R 表示红球,B 表示蓝球,在 b≤r<b+nb \le r < b+n 的情况下,一种方案满足条件当且仅当它能取出 n−(r−b)n-(r-b) 对 RB 的子序列。

等价于,将 R 看成 +1+1,B 看成 −1-1,任意一个前缀和都要 ≥n−r\ge n - r。

等价于从 (0,0)(0,0) 开始每次向右或者向上走到 (r,b)(r,b),但是不能到达 y=x+(r−n)y = x + (r - n) 的严格上方(即碰到 y=x+(r−n)+1y = x+(r-n)+1 这条线)的方案数。

这是一类经典的组合问题,用类似卡特兰数的计算方法(翻转从起点到第一次碰到 y=x+(r−n)+1y = x+(r-n)+1 的部分,每条不合法的路径与从 (n−r−1,r−n+1)(n-r-1,r-n+1) 到 (r,b)(r,b) 的路径一一对应)可得方案数为: (r+br)−(r+b2r−n+1) \binom {r+b}r - \binom{r+b}{2r-n+1} 于是枚举 rr 即可,注意 r=br=b 的情况需要特判一下(直接将 bb 减 11 即可,因为最后一个必须要是 B),时间复杂度 O(n)\mathcal O(n)。

代码

int main() {
	int n, k;
	rd(n, k);
	if (k < n) return print(0), 0;
	init(k);
	modint ans;
	for (int r = 0; r <= k; r++) {
		int b = k - r;
		if (r < b) continue;
		if (r == b) --b;
		ans += binom(r + b, r) - binom(r + b, 2 * r - n + 1);
	}
	print(ans);
	return 0;
}

Trinity

题意

  • 有一个 n×mn \times m 的网格,每个格子上可以染为黑色或白色。
  • 求满足下列条件的三元组 (a1…n,b1…m,c1…m)(a_{1\dots n}, b_{1\dots m}, c_{1\dots m}) 的个数。
    • aia_i 为最小的 jj 满足 (i,j)(i,j) 为黑色,若不存在则 ai=m+1a_i = m + 1。
    • bib_i 为最小的 jj 满足 (j,i)(j,i) 为黑色,若不存在则 bi=n+1b_i = n + 1。
    • cic_i 为最大的 jj 满足 (j,i)(j,i) 为黑色,若不存在则 ci=0c_i = 0。
  • n≤8×103n \le 8 \times 10^3,m≤200m \le 200,答案对 998244353998244353 取模。

题解

考虑 DP,设 fi,jf_{i,j} 表示 ii 行 jj 列且每行至少有一个黑色格子的三元组个数。则答案为 ∑i=0n(ni)fi,m\sum_{i=0}^n \binom ni f_{i,m}。

考虑转移,每次新增 kk 行同时扩展一列,其中新增的 kk 行的前 jj 列都是白格子,扩展一列黑格子,原有的 ii 行扩展的一列可黑可白。注意这里新增的 kk 行可以插入在原有的 ii 行中间,而扩展一列是向后扩展。这样转移会包括所有可能的染色方案且没有重复,这是因为对于一种染色方案,DP 的路径有且仅有一条,即对于染色方案前 jj 列,有状态 fi,jf_{i,j},其中 ii 为前 jj 列有黑色格子的行数。

考虑转移的系数:

  • 若 k=0k = 0,则相当于在第 j+1j+1 列选 0/1/20/1/2 个端点,系数为 1+i+(i2)1+i+\binom i2。
  • 若 k>0k > 0,由于可能插入到中间,因此考虑第 j+1j+1 列的 b−1b-1 和 c+1c+1,一定不与新增的 kk 行重合。贡献计数为 (i+k+2k+2)\binom {i+k+2}{k+2},意义为从 [0,i+k+1][0,i+k+1] 中选择 k+2k+2 行,其中第一行和最后一行分别作为 b−1b-1 和 c+1c+1,中间 kk 行作为新增的 kk 行。

综上: fi,j=(1+i+(i2))fi,j−1+∑k=0i−1(i+2i−k+2)fk,j−1 f_{i,j} = \left(1+i+\binom i2\right)f_{i,j-1} + \sum_{k=0}^{i-1} \binom{i+2}{i-k+2}f_{k,j-1} 时间复杂度 O(n2m)\mathcal O(n^2m),无法接受。

考虑转移后半部分为卷积的形式: =∑k=0i−1(i+2)!(i−k+2)!k!fk,j−1=(i+2)!∑k=0i−11(i−k+2)!⋅fk,j−1k! \begin{aligned} &= \sum_{k=0}^{i-1} \frac{(i+2)!}{(i-k+2)!k!}f_{k,j-1} \\\\ &= (i+2)!\sum_{k=0}^{i-1} \frac1{(i-k+2)!} \cdot \frac{f_{k,j-1}}{k!} \end{aligned} NTT 优化时间复杂度为 O(nmlog⁡n)\mathcal O(nm\log n)。

代码

namespace NTT {
	const int N = 1 << 21 | 1;
	const modint g = 3, vg = 1 / g;
	int n, m, k, l, r[N];
	modint vk, a[N], b[N];
	inline void ntt(modint *a, int n, modint x) {
		for (int i = 0; i < n; i++)
			if (i < r[i]) swap(a[i], a[r[i]]);
		for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1) {
			modint W = x ^ ((P - 1) / o);
			for (int i = 0; i < n; i += o) {
				modint w = 1;
				for (int j = 0; j < k; j++, w *= W) {
					modint x = a[i+j], y = a[i+j+k] * w;
					a[i+j] = x + y, a[i+j+k] = x - y;
				}
			}
		}
	}
	inline void solve() {
		k = 1, l = 0;
		while (k <= n + m) k <<= 1, ++l;
		vk = (modint)1 / k;
		for (int i = 0; i < k; i++)
			r[i] = r[i>>1] >> 1 | (i & 1) << (l - 1);
		for (int i = n + 1; i < k; i++) a[i] = 0;
		for (int i = m + 1; i < k; i++) b[i] = 0;
		ntt(a, k, g), ntt(b, k, g);
		for (int i = 0; i < k; i++) a[i] *= b[i];
		ntt(a, k, vg);
		for (int i = 0; i <= n + m; i++) a[i] *= vk;
	}
}

const int N = 8e3 + 7, M = 207;
int n, m;
modint f[N][M], ans;

int main() {
	rd(n, m), init(n + 2);
	f[0][0] = 1;
	for (int j = 0; j < m; j++) {
		NTT::n = NTT::m = n;
		for (int i = 0; i <= n; i++) NTT::a[i] = f[i][j] * vp[i];
		NTT::b[0] = 0;
		for (int i = 1; i <= n; i++) NTT::b[i] = vp[i+2];
		NTT::solve();
		for (int i = 0; i <= n; i++)
			f[i][j+1] = (1 + i + binom(i, 2)) * f[i][j] + p[i+2] * NTT::a[i];
	}
	for (int i = 0; i <= n; i++)
		ans += binom(n, i) * f[i][m];
	print(ans);
	return 0;
}

标签:DP、NTT、凸包、区间 DP、构造、组合计数、计算几何、记忆化搜索、贪心

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…