「杂题乱写」AGC 004

发布时间 2023-06-11 15:33:34作者: K8He

「杂题乱写」AGC 004

点击查看目录

AGC 题目真挺小清新的。

一般来说只要有一个突破点就可以做出来,但是并不好想,感觉比较锻炼思维。

写题感觉思维上不去了可以来做做,挺愉悦身心的。

A | Divide a Cuboid

存在偶数可分成相等两块,不存在选两个乘积最小的。

B | Colorful Slimes

考虑枚举操作二次数。

WA 了一次,原因是 inf = 1ll << 40 不够大 ?

C | AND Grid

分别涂第一列和最后一列,奇数行和偶数行。

D | Teleporter

显然直接把 \(1\) 连向自己就可以。

然后把原图划分成多个子树连向 \(1\),每个子树深度不超过 \(k\)

点击查看代码
const ll N = 1e5 + 10;
namespace SOLVE {
	ll n, k, dep[N], vis[N], ans;
	std::vector <ll> tu[N];
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline void Dfs (ll u, ll fa) {
		far (v, tu[u]) {
			if (v == fa) continue;
			Dfs (v, u), dep[u] = std::max (dep[u], dep[v] + 1);
		}
		if (dep[u] == k - 1 && u != 1 && fa != 1) ++ans, dep[u] = -1;
		return;
	}
	inline void In () {
		n = rnt (), k = rnt (), ans = (rnt () != 1);
		_for (i, 2, n) tu[rnt ()].push_back (i);
		return;
	}
	inline void Solve () {
		Dfs (1, 0);
		return;
	}
	inline void Out () {
		printf ("%lld\n", ans);
		return;
	}
}

E | Salvage Robots

刚开始是觉得一个矩形只要不超过边界就都一定都可以到达出口。错的,有一个 Hack:

Hack 有乱劈的意思
5 5
.oo.o
..o.o
..oo.
E....
.o..o

正解是 DP。

\(f_{l, r, u, d}\) 表示清理过了左边 \(l\) 个方格,右边 \(r\) 个方格,上边 \(u\) 个方格,下边 \(d\) 个方格。

image

注意到移动后会有一些机器人被推出去(下图红框内的),转移时要判断一下。

image

\(s_{i1, j1, i2, j2}\) 表示左上角为 \((i1, j1)\),右下角为 \((i2, j2)\) 的矩形中有多少个机器人。

转移就是:

\[\begin{aligned} f_{l + 1, r, u, d} &\leftarrow \max \{f_{l + 1, r, u, d}, f_{l, r, u, d} + s_{\max \{i1 - 1, d\}, j1 - 1, \min \{i2, n - u\}, j1 - 1}\}\\ f_{l, r + 1, u, d} &\leftarrow \max \{f_{l, r + 1, u, d}, f_{l, r, u, d} + s_{\max \{i1 - 1, d\}, j2 + 1, \min \{i2, n - u\}, j2 + 1}\}\\ f_{l, r, u + 1, d} &\leftarrow \max \{f_{l, r, u + 1, d}, f_{l, r, u, d} + s_{i1 - 1, \max \{j1 - 1, r\}, i1 - 1, \min \{j2, m - l\}}\}\\ f_{l, r, u, d + 1} &\leftarrow \max \{f_{l, r, u, d + 1}, f_{l, r, u, d} + s_{i2 + 1, \max \{j1 - 1, r\}, i2 + 1, \min \{j2, m - l\}}\}\\ \end{aligned} \]

用下图感性理解:

image

点击查看代码
const ll N = 110;
namespace SOLVE {
	ll n, m, ex, ey, cnt[2][N][N], f[N][N][N][N], ans; char s[N][N];
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline void In () {
		n = rnt (), m = rnt ();
		_for (i, 1, n) scanf ("%s", s[i] + 1);
		return;
	}
	inline void Solve () {
		_for (i, 1, n) {
			_for (j, 1, m) {
				if (s[i][j] == 'E') ex = i, ey = j;
				cnt[0][i][j] = cnt[0][i][j - 1] + (s[i][j] == 'o');
				cnt[1][i][j] = cnt[1][i - 1][j] + (s[i][j] == 'o');
			}
		}
		_for (l, 0, ey - 1) {
			_for (r, 0, m - ey) {
				_for (u, 0, ex - 1) {
					_for (d, 0, n - ex) {
						ans = std::max (ans, f[l][r][u][d]);
						ll i1 = ex - u, i2 = ex + d, j1 = ey - l, j2 = ey + r;
						if (l + r < ey - 1) f[l + 1][r][u][d] = std::max (f[l + 1][r][u][d], f[l][r][u][d] + cnt[1][std::min (i2, n - u)][j1 - 1] - cnt[1][std::max (i1 - 1, d)][j1 - 1]);
						if (l + r < m - ey) f[l][r + 1][u][d] = std::max (f[l][r + 1][u][d], f[l][r][u][d] + cnt[1][std::min (i2, n - u)][j2 + 1] - cnt[1][std::max (i1 - 1, d)][j2 + 1]);
						if (u + d < ex - 1) f[l][r][u + 1][d] = std::max (f[l][r][u + 1][d], f[l][r][u][d] + cnt[0][i1 - 1][std::min (j2, m - l)] - cnt[0][i1 - 1][std::max (j1 - 1, r)]);
						if (u + d < n - ex) f[l][r][u][d + 1] = std::max (f[l][r][u][d + 1], f[l][r][u][d] + cnt[0][i2 + 1][std::min (j2, m - l)] - cnt[0][i2 + 1][std::max (j1 - 1, r)]);
					}
				}
			}
		}
		return;
	}
	inline void Out () {
		printf ("%lld\n", ans);
		return;
	}
}

F | Namori

好难啊。好难啊。好难啊。好难啊。好难啊。好难啊。好难啊。好难啊。好难啊。

看 gtm1514 老师题解看懂的 /bx /bx /bx

这里就口胡一下,你要真想看懂就别看我的。

数据范围 \(N - 1\le M\le N\),所以图是一个树或基环树!

考虑转化题意,可以转化为把图黑白交替染色,于是操作就变成了交换黑白点,目标是黑白对调。

AGC 004 F

\(n\) 为奇数显然无解。

先从树入手。假设以 \(u\) 节点为根的子树有 \(x\) 个黑点,\(y\) 个白点,那么 \(u\) 通往父亲的边至少会被经过 \(a_u = \left|x - y\right|\) 次,求和即可。

然后考虑偶环基环树。我们断一条边,然后考虑环的贡献。操作 \(x\) 次这条边的话,环上会有一些边多向上跑 \(x\) 次,一些边多向下跑 \(x\) 次。维护一个 \(k\) 数组标记左右半环,断的边左端点标记 \(1\),右端点标记 \(-1\),环上答案应该是 \(\sum_{u\text{ on the ring}}\left|a_ik_i - x\right|\)

现在问题是 \(x\) 取什么能让这个式子值最小,答案是中位数。

最后考虑奇环基环树。染色后一定会有一条边两端点颜色相同,操作这条边相当于加上/减少两个黑点,那么设树上共有 \(x\) 个黑点,\(y\) 个白点,那么操作 \(\frac{\left|x - y\right|}{2}\) 次即可平衡黑白点数量。

注意到只有奇环基环树可以平衡黑白点数量,所以如果普通树/偶环基环树黑白点数量不一样也无解。

点击查看代码
const ll N = 1e5 + 10;
namespace SOLVE {
	ll n, m, c[N], zhiyin, e[2], k[N], ring[N], ans;
	std::vector <ll> tu[N];
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline void Paint (ll u, ll fa, ll color) {
		c[u] = color;
		far (v, tu[u]) {
			if (v == fa) continue;
			if (!c[v]) Paint (v, u, -color);
			else {
				if (c[u] == c[v]) zhiyin = 1;
				e[0] = u, e[1] = v;
			}
		}
		return;
	}
	inline void GetAns (ll u, ll fa) {
		far (v, tu[u]) {
			if (v == fa || (u == e[0] && v == e[1]) || (u == e[1] && v == e[0])) continue;
			GetAns (v, u), c[u] += c[v], k[u] += k[v];
		}
		return;
	}
	inline void In () {
		n = rnt (), m = rnt ();
		_for (i, 1, m) {
			ll u = rnt (), v = rnt ();
			tu[u].push_back (v);
			tu[v].push_back (u);
		}
		return;
	}
	inline void Solve () {
		if (n & 1) { ans = -1; return; }
		Paint (1, 0, 1);
		ll cnt = 0;
		_for (i, 1, n) cnt += c[i];
		if (m == n - 1 && cnt) { ans = -1; return; }
		if (m == n) {
			if (zhiyin) ans += std::abs (cnt / 2), c[e[0]] -= cnt / 2, c[e[1]] -= cnt / 2;
			else  {
				if (cnt) { ans = -1; return; }
				else k[e[0]] = 1, k[e[1]] = -1;
			}
		}
		GetAns (1, 0);
		_for (i, 1, n) {
			if (k[i]) ring[++ring[0]] = c[i] * k[i];
			else ans += std::abs (c[i]);
		}
		ring[++ring[0]] = 0;
		std::sort (ring + 1, ring + ring[0] + 1);
		ll mid = ring[(ring[0] + 1) >> 1];
		_for (i, 1, ring[0]) ans += std::abs (ring[i] - mid);
		return;
	}
	inline void Out () {
		printf ("%lld\n", ans);
		return;
	}
}