← 全部文章

AtCoder Grand Contest 039 题解

本文目录(15 节)
  1. Connection and Disconnection
  2. Graph Partition
  3. Division by Two with Something
  4. Incenters
  5. 题意
  6. 题解
  7. 代码
  8. Pairing Points
  9. 题意
  10. 题解
  11. 代码
  12. Min Product Sum
  13. 题意
  14. 题解
  15. 代码

AtCoder Grand Contest 039

Connection and Disconnection

分类讨论贪心。

const int N = 107;
int n, c, k, t, a, b;
char s[N];

int main() {
	rds(s, n), rd(k);
	for (int l = 1, r = 1; l <= n; l = ++r) {
		while (s[r+1] == s[l]) ++r;
		t += (r - l + 1) >> 1, ++c;
		if (!a) a = r - l + 1;
		b = r - l + 1;
	}
	if (s[1] != s[n]) print(1ll * k * t);
	else if (c == 1) print((1ll * n * k) >> 1);
	else {
		t -= (a >> 1) + (b >> 1);
		t += (a + b) >> 1;
		print(1ll * k * t - ((a & 1) && (b & 1)));
	}
	return 0;
}

Graph Partition

首先图要是一张二分图。

是二分图的情况下,从某一个点开始 bfs,如果合法,用最大深度更新答案即可。

时间复杂度 O(n3)\mathcal O(n^3)。

const int N = 207;
int n, c[N], d[N], ans;
vi e[N];

void dfs(int x, int o) {
	c[x] = o;
	for (int y : e[x])
		if (!c[y]) dfs(y, 3 - o);
		else if (c[x] == c[y]) print(-1), exit(0);
}

inline int calc(int s) {
	for (int i = 1; i <= n; i++) d[i] = 0;
	d[s] = 1;
	queue<int> q;
	q.push(s);
	while (q.size()) {
		int x = q.front();
		q.pop();
		for (int y : e[x])
			if (!d[y]) d[y] = d[x] + 1, q.push(y);
			else if (d[y] != d[x] - 1 && d[y] != d[x] + 1) return 0;
	}
	return *max_element(d + 1, d + n + 1);
}

int main() {
	rd(n);
	for (int x = 1; x <= n; x++)
		for (int y = 1; y <= n; y++) {
			char c;
			rdc(c);
			if (c == '1') e[x].pb(y);
		}
	dfs(1, 1);
	for (int i = 1; i <= n; i++) ans = max(ans, calc(i));
	print(ans);
	return 0;
}

Division by Two with Something

有趣的计数题。

不当成一个 nn 位二进制数而看作一个 0/10/1 串的话,每次操作相当于把最右边的字符取反放到最左边。

考虑一个 0/10/1 串如何计算需要多少次操作能回到本身。

首先注意到串 ss 一定会至多经过 nn 次操作完全取反,然后再经过同等次数的操作回到本身。

因此任何一个串一定能够回到本身,且次数不超过 2n2n 次,且次数为偶数次。

假设次数为 2t2t 次,则意味着 tt 次之后能取反。

于是有 s[n−t+1:n]s[n-t+1:n] 和 s[n−2t+1:n−t]s[n-2t+1:n-t] 互反,s[n−2t+1:n−t]s[n-2t+1:n-t] 和 s[n−3t+1:n−2t]s[n-3t+1:n-2t] 互反,⋯\cdots,s[t+1:2t]s[t+1:2t] 与 s[1:t]s[1:t] 互反,s[1:t]s[1:t] 与 s[n−t+1:n]s[n-t+1:n] 互反。

因此 tt 为满足:

  1. nn 的约数;
  2. nt\frac nt 为奇数;
  3. s[i]s[i] 与 s[i+t]s[i+t] 互反;

的最小值。

于是就简单了,枚举 tt,求出恰好经过 tt 次操作回到本身且 ≤X\le X 的个数,即:

  • S[1:t]S[1:t] 所构成的数;
  • 减去 tt 的约数对应的个数(这意味着更小的循环节);
  • 如果 S[1:t]S[1:t] 根据 s[i]s[i] 与 s[i+t]s[i+t] 互反,扩展成长度为 nn 的数之后 ≤X\le X,则还要加上 11。

然后将个数乘上 2t2t 就是 tt 的贡献。

时间复杂度 O(nn)\mathcal O(n \sqrt n)。

const int N = 2e5 + 7;
int n;
char s[N];
bool a[N], b[N];
modint c[N], ans;

inline bool pd(int i) {
	for (int j = 1; j <= i; j++) b[j] = a[j];
	for (int j = i + 1; j <= n; j++) {
		b[j] = b[j-i] ^ 1;
		if (b[j] < a[j]) return 1;
		if (b[j] > a[j]) return 0;
	}
	return 1;
}

int main() {
	rd(n), rds(s, n);
	for (int i = 1; i <= n; i++) a[i] = s[i] & 15;
	for (int i = 1; i <= n; i++)
		if (!(n % i) && ((n / i) & 1)) {
			for (int j = 1; j <= i; j++)
				c[i] = c[i] * 2 + a[j];
			for (int j = 1; j < i; j++)
				if (!(i % j)) c[i] -= c[j];
			c[i] += pd(i);
			ans += c[i] * i * 2;
		}
	print(ans);
	return 0;
}

Incenters

题意

  • 平面直角坐标系上有 nn 个点,第 ii 个点为 (cos⁡(2πTiL),sin⁡(2πTiL))(\cos(\frac {2\pi T_i}{L}), \sin(\frac {2\pi T_i}{L}))。
  • 求出在 nn 个点中随机选择三个点,以这三个点为顶点的三角形的内心的期望坐标。
  • n≤3×103n \le 3 \times 10^3,0≤Ti<L≤1090 \le T_i < L \le 10^9。

题解

对于 △ABC\triangle ABC,取 AB,BC,CAAB,BC,CA 弧的中点 D,E,FD,E,F,可以发现 △DEF\triangle DEF 的垂心就是 △ABC\triangle ABC 的内心。

考虑如何求 △DEF\triangle DEF 的垂心,根据欧拉线,由于 △DEF\triangle DEF 的外心为 O(0,0)O(0,0),重心为 D+E+F3\frac{D+E+F}{3},所以垂心为 D+E+FD+E+F。

于是显然可以 O(n2)\mathcal O(n^2) 枚举求答案。

代码

const int N = 3e3 + 7;
const ld PI = acos(-1);
int n, L, a[N];
ld x, y;

int main() {
	rd(n, L), rda(a, n);
	for (int i = 1; i < n; i++)
		for (int j = i + 1; j <= n; j++)
			x += (n - 2 * (j - i)) * cos(PI * (a[i] + a[j]) / L),
			y += (n - 2 * (j - i)) * sin(PI * (a[i] + a[j]) / L);
	ll k = 1ll * n * (n - 1) * (n - 2) / 6;
	printf("%.20Lf %.20Lf\n", x / k, y / k);
	return 0;
}

Pairing Points

题意

  • 圆上有 2n2n 个点,对于任意六个点 a,b,c,d,e,fa,b,c,d,e,f 满足 ab,cd,efab,cd,ef 三线不共点。
  • 给定一个 2n×2n2n \times 2n 的 0/10/1 矩阵 aa,你需要计算将 2n2n 个点分成 nn 个点对的方案数,满足:
    • 对于所有点对 (p,q)(p,q),ap,q=aq,p=1a_{p,q} = a_{q,p} = 1。
    • 将所有点对用线段连接起来,恰好形成一棵树。
  • n≤20n \le 20。

题解

考虑先将 nn 设为原本的 2n2n。

考虑用 f(l,m,r)f(l,m,r) 表示在 [l,m−1]∪[m+1,r][l,m-1] \cup [m+1,r] 中连一些点对,使得至少有一组点对跨过 mm,且所有跨过 mm 的点对两两不交的方案数。

枚举 11 号点的对应点 ii,于是问题求 f(2,i,n)f(2,i,n) 的答案。

考虑如何求 f(l,m,r)f(l,m,r),枚举跨过 mm 的所有点对中最靠近 l,rl,r 的点对 (p,q)(p,q)。

可以发现,此时 [p+1,m−1][p+1,m-1] 中,有靠近 pp 的一部分会和 pp 连通,剩下的部分与 mm 连通,分界点为 xx。

同理,此时 [m+1,q−1][m+1,q-1] 中,有靠近 qq 的一部分会和 qq 连通,剩下的部分与 mm 连通,分界点为 yy。

而 [l,p−1][l,p-1] 一定和 pp 连通,[q+1,r][q+1,r] 一定和 qq 连通。

因此问题被分为了 f(l,p,x),f(y,q,r),f(x+1,m,y−1)f(l,p,x),f(y,q,r),f(x+1,m,y-1) 三个子问题。

记忆化搜索即可,状态数为 O(n3)\mathcal O(n^3),转移为 O(n4)\mathcal O(n^4),总时间复杂度为 O(n7)\mathcal O(n^7),常数非常小,可以通过本题。

这个 DP 可以优化到 O(n5)\mathcal O(n^5),不过没必要。

代码

const int N = 47;
int n, a[N][N];
char s[N][N];
ll f[N][N][N], ans;
bool v[N][N][N];

ll DP(int l, int m, int r) {
	if ((l ^ r) & 1) return 0;
	if (l == m && m == r) return 1;
	if (l == m || m == r) return 0;
	if (v[l][m][r]) return f[l][m][r];
	v[l][m][r] = 1;
	for (int p = l; p < m; p++)
		for (int q = r; q > m; q--)
			if (a[p][q])
				for (int x = p; x < m; x++)
					for (int y = q; y > m; y--)
						f[l][m][r] += DP(l, p, x) * DP(y, q, r) * DP(x + 1, m, y - 1);
	return f[l][m][r];
}

int main() {
	rd(n), n <<= 1;
	for (int i = 1; i <= n; i++) rds(s[i], n);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			a[i][j] = s[i][j] & 1;
	for (int i = 2; i <= n; i++)
		if (a[1][i]) ans += DP(2, i, n);
	print(ans);
	return 0;
}

Min Product Sum

题意

  • 有一个 n×mn \times m 的矩阵 aa,每个位置上有一个 [1,k][1,k] 的整数。
  • 对于一种填数方案,设 f(x,y)=min⁡(min⁡i=1nai,y,min⁡j=1max,j)f(x,y) = \min(\min_{i=1}^n a_{i,y}, \min_{j=1}^m a_{x,j}),则这种方案的权值为 ∏x=1n∏y=1mf(x,y)\prod_{x=1}^n \prod_{y=1}^m f(x,y)。
  • 求一共 knmk^{nm} 种方案的权值和。
  • n,m,k≤100n,m,k \le 100,答案对 PP 取模。

题解

首先转化题意:

  • 有两个 n×mn \times m 的矩阵 a,ba,b,每个位置上有一个 [1,k][1,k] 的整数。
  • 另外要求 bx,y≤min⁡(min⁡i=1nai,y,min⁡j=1max,j)b_{x,y} \le \min(\min_{i=1}^n a_{i,y}, \min_{j=1}^m a_{x,j})。
  • 求方案数。

显然这两者是等价的。

设 xi=min⁡j=1mai,jx_i = \min_{j=1}^m a_{i,j},yj=min⁡i=1nai,jy_j = \min_{i=1}^n a_{i,j},假设我们已经设定了所有 xi,yjx_i,y_j,考虑如何求方案数。

对于 aa,ai,ja_{i,j} 可以填 [max⁡(xi,yj),k][\max(x_i, y_j), k];对于 bb,bi,jb_{i,j} 可以填 [1,min⁡(xi,yi)][1,\min(x_i,y_i)]。

然而这样填是有问题的,并不能保证最小值恰好 xi,yjx_i,y_j,于是需要容斥一下,即枚举有多少行多少列的最小值大于设定值,乘上 −1-1 的那么多次方。

考虑 DP,从小到大填 xi,yix_i,y_i,设 ft,i,jf_{t,i,j} 表示已经填了 1∼t1 \sim t,用了 ii 行 jj 列,aa 已经填了这 ii 行 jj 列的交的位置,bb 已经填了这 ii 行 jj 列的并的位置,的方案数。

转移时枚举填 tt 的不被容斥的行列数 p,qp,q,被容斥的行列数 u,vu,v,则有: ft,i+p+u,j+q+v←(−1)u+v(n−ip,u)(m−jq,v)⋅(k−t+1)iq+pj+pq(k−t)iv+pv+uj+uq+uvt(p+u)(m−j)+(n−i)(q+v)−(p+u)(q+v)⋅ft−1,i,j f_{t,i+p+u,j+q+v} \leftarrow (-1)^{u+v} \binom {n-i}{p,u} \binom {m-j}{q,v} \cdot (k-t+1)^{iq+pj+pq}(k-t)^{iv+pv+uj+uq+uv}t^{(p+u)(m-j)+(n-i)(q+v)-(p+u)(q+v)} \cdot f_{t-1,i,j} 然而设 n,m,kn,m,k 同阶,这个 DP 复杂度是 O(n7)\mathcal O(n^7),显然要优化。

依然从小到大枚举 tt,在这一层转移时,可以发现我们不必同时枚举 p,q,u,vp,q,u,v,而只需要依次枚举,每次把贡献计算清楚即可。

具体而言,先让 ftf_t 继承 ft−1f_{t-1}。

再枚举 pp,转移为: ft,i+p,j←(n−ip)⋅(k−t+1)pjtp(m−j)⋅ft,i,j f_{t,i+p,j} \leftarrow \binom {n-i}{p} \cdot (k-t+1)^{pj}t^{p(m-j)} \cdot f_{t,i,j} 再枚举 qq,转移为: ft,i,j+q←(m−jq)⋅(k−t+1)iqtq(n−i)⋅ft,i,j f_{t,i,j+q} \leftarrow \binom {m-j}{q} \cdot (k-t+1)^{iq}t^{q(n-i)} \cdot f_{t,i,j} 再枚举 uu,转移为: ft,i+u,j←(−1)u(n−iu)⋅(k−t)ujtu(m−j)⋅ft,i,j f_{t,i+u,j} \leftarrow (-1)^u\binom {n-i}{u} \cdot (k-t)^{uj}t^{u(m-j)} \cdot f_{t,i,j} 再枚举 vv,转移为: ft,i,j+v←(−1)v(m−jv)⋅(k−t)ivtv(n−i)⋅ft,i,j f_{t,i,j+v} \leftarrow (-1)^v\binom {m-j}{v} \cdot (k-t)^{iv}t^{v(n-i)} \cdot f_{t,i,j} 时间复杂度 O(n4)\mathcal O(n^4),很卡常,要进行一定程度的预处理,尽可能减少取模次数。

代码

const int N = 107;
int n, m, k;
int f[N<<2][N][N], g[N][N*N], c[N][N];

inline void upd(int &x, int y, int z) {
	x = (x + 1ll * y * z) % P;
}

int main() {
	rd(n, m, k), rd(P);
	init(max(n, m));
	for (int i = 0; i <= k; i++) {
		g[i][0] = 1;
		for (int j = 1; j <= n * m; j++) g[i][j] = 1ll * g[i][j-1] * i % P;
	}
	for (int i = 0; i <= max(n, m); i++)
		for (int j = 0; j <= i; j++) c[i][j] = binom(i, j).x;
	f[0][0][0] = 1;
	for (int t = 1, o = 0; t <= k; t++) {
		for (int i = 0; i <= n; i++)
			for (int j = 0; j <= m; j++)
				if (!!f[o][i][j]) {
					ll x = f[o][i][j], y = 1ll * g[k-t+1][j] * g[t][m-j] % P;
					for (int p = 0; i + p <= n; p++, (x *= y) %= P) upd(f[o+1][i+p][j], c[n-i][p], x);
				}
		++o;
		for (int i = 0; i <= n; i++)
			for (int j = 0; j <= m; j++)
				if (!!f[o][i][j]) {
					ll x = f[o][i][j], y = 1ll * g[k-t+1][i] * g[t][n-i] % P;
					for (int q = 0; j + q <= m; q++, (x *= y) %= P) upd(f[o+1][i][j+q], c[m-j][q], x);
				}
		++o;
		for (int i = 0; i <= n; i++)
			for (int j = 0; j <= m; j++)
				if (!!f[o][i][j]) {
					ll x = f[o][i][j], y = P - 1ll * g[k-t][j] * g[t][m-j] % P;
					for (int u = 0; i + u <= n; u++, (x *= y) %= P) upd(f[o+1][i+u][j], c[n-i][u], x);
				}
		++o;
		for (int i = 0; i <= n; i++)
			for (int j = 0; j <= m; j++)
				if (!!f[o][i][j]) {
					ll x = f[o][i][j], y = P - 1ll * g[k-t][i] * g[t][n-i] % P;
					for (int v = 0; j + v <= m; v++, (x *= y) %= P) upd(f[o+1][i][j+v], c[m-j][v], x);
				}
		++o;
	}
	print(f[k<<2][n][m]);
	return 0;
}

标签:DP、二分图、容斥、数论、组合计数、计算几何、记忆化搜索、贪心

评论

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

    正在连接评论服务…