A - ABC143C

给定一串由小写字母组成的字符串 $S$,两个相同的相邻字母可以合并,问最后字符串的长度。

分析

从前往后枚举,只要当前字母和下一个字母不同就让 $ans + 1$。

int main() {
	int n;
	string s;
	cin >> n >> s;
	s.push_back('$');
	int ans = 0;
	for (int i = 1; i < s.size(); i++) {
		if (s[i] != s[i - 1]) {
			ans += 1;
		}
	}
	cout << ans;
	return 0;
}

B - CF451B

给定长度为 $n$ 的不含相同数字的序列,判断是否可以翻转一段子区间,使这个数组递增。

分析

寻找序列的拐点。如果拐点数量不等于 $2$,则答案是显然的。

如果恰有两个拐点,则反转后判断一下是否排序即可。

int main() {
	int n;
	cin >> n;
	vector<ll> v(n + 2);
	v[0] = -1, v[n + 1] = 1E9 + 9;
	for (int i = 1; i <= n; i++) {
		cin >> v[i];
	}
	vector<int> pos;
	for (int i = 1; i <= n; i++) {
		if ((v[i] - v[i - 1]) * (v[i + 1] - v[i]) < 0) {
			pos.push_back(i);
		}
	}
	if (pos.size() == 0) {
		cout << "yes\n1 1";
	} else if (pos.size() == 1) {
		cout << "yes\n1 " << pos[0];
	} else if (pos.size() == 2) {
		int l = pos[0], r = pos[1];
		std::reverse(v.begin() + l, v.begin() + r + 1);
		if (std::is_sorted(v.begin(), v.end())) {
			cout << "yes\n" << l << " " << r;
		} else {
			cout << "no";
		}
	} else {
		cout << "no";
	}

	return 0;
}

C -ABC146B

给定一棵树,将所有边染色,构造一种方案使得每个点的出边颜色不同且使用的颜色种类最少。

分析:将边权下放到点权,该点记录的是它代表的边的颜色,然后将该点的儿子从 $1$ 开始染色,如果颜色和该点相同则让颜色种类加 $1$,即用父节点的信息去更新子节点。

const int N = 1E5 + 5;
vector<pair<int, int>> G[N];

int main() {
	int n;
	cin >> n;
	for (int i = 1; i < n; i++) {
		int u, v;
		cin >> u >> v;
		G[u].push_back({v, i});
		G[v].push_back({u, i});
	}
	vector<int> color(n + 1), cans(n + 1);
	int k = 1;
	std::function<void(int, int)> dfs = [&](int u, int fa) {
		int ci = 1;
		for (auto [v, i] : G[u]) {
			if (v != fa) {
				if (color[u] == ci) {
					ci++;
				}
				color[v] = ci;
				cans[i] = ci;
				ci++;
				dfs(v, u);
			}
		}
		k = max(k, ci - 1);
	};
	color[1] = 0;
	dfs(1, 0);
	cout << k << endl;
	for (int i = 1; i < n; i++) {
		cout << cans[i] << endl;
	}
	return 0;
}

D - CF191C

给定一棵树,$k$ 次操作,每次操作将从 $x$ 到 $y$ 的路径上的边权加 $1$,问每条边的边权最后是多少。

分析

树上差分。对于每一次操作,将 $d[x] + 1$,$d[y] + 1$,$d[lca(x, y)] - 2$,然后 DFS 一遍,将 $d$ 从儿子到父亲累加起来,最后每个 $d$ 存的就是它所代表边权的答案。这道题也可以用树链剖分去做。

#define int long long
const int N = 200010;
int n, m;
int depth[N], fa[N][23], ans[N], d[N];
map<pair<int, int>, int> mp;
vector<int> g[N];

void bfs() {
	queue<int> q;
	q.push(1);
	memset(depth, 0x3f, sizeof depth);
	depth[0] = 0, depth[1] = 1;
	while (q.size()) {
		int t = q.front();
		q.pop();
		for (int to : g[t]) {
			if (depth[to] > depth[t] + 1) {
				q.push(to);
				depth[to] = depth[t] + 1;
				fa[to][0] = t;
				for (int k = 1; k <= 21; k++)
					fa[to][k] = fa[fa[to][k - 1]][k - 1];
			}
		}
	}
}

int lca(int a, int b) {
	if (depth[a] < depth[b])
		swap(a, b);
	for (int k = 21; k >= 0; k--)
		if (depth[fa[a][k]] >= depth[b])
			a = fa[a][k];
	if (a == b)
		return a;
	for (int k = 21; k >= 0; k--)
		if (fa[a][k] != fa[b][k]) {
			a = fa[a][k];
			b = fa[b][k];
		}
	return fa[a][0];
}

void dfs(int u, int fa) {
	for (int to : g[u]) {
		if (to == fa)
			continue;
		dfs(to, u);
		d[u] += d[to];
		ans[mp[{min(u, to), max(u, to)}]] = d[to];
	}
}

int32_t main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> n;
	for (int i = 1, a, b; i < n; i++) {
		cin >> a >> b;
		g[a].push_back(b), g[b].push_back(a);
		mp[{min(a, b), max(a, b)}] = i;
	}
	bfs();
	cin >> m;
	while (m--) {
		int x, y;
		cin >> x >> y;
		int p = lca(x, y);
		d[x]++, d[y]++, d[p] -= 2;
	}
	dfs(1, -1);
	for (int i = 1; i < n; i++)
		cout << ans[i] << " \n"[i + 1 == n];

	return 0;
}

E - CF142A

给定

$$ N = (A - 1) (B - 2) (C - 2) $$

求 $ABC-N$ 的最大值和最小值。

分析

虽然 $N$ 最大有 $10^9$,但是 $N$ 可以被拆分成三个数相乘,说明最小因数是 $10^3$ 级别的。

所以可以通过枚举因数来找到最大值和最小值。

int main() {
	ll n;
	cin >> n;
	vector<ll> v;
	for (int i = 1; 1ll * i * i <= n; i++) {
		if (n % i == 0) {
			v.push_back(i);
			v.push_back(n / i);
		}
	}
	sort(v.begin(), v.end());
	ll mi = 1E15, ma = -1E15;
	for (auto i : v) {
		for (auto j : v) {
			if (n % (i * j) == 0) {
				ll k = n / i / j;
				ll calc = (i + 1) * (j + 2) * (k + 2) - n;
				ma = max(ma, calc);
				mi = min(mi, calc);
			}
		}
	}
	cout << mi << ' ' << ma;

	return 0;
}

F - CF479E

有一个 $N$ 层的楼,起点是 $a$,其中 $b$ 点是不能走的。假设当前在 $x$ 层,所能走到的楼层 $y$ 必须满足

$$ |x - y| < |x - b| $$

且不能停留在原地,一共走 $k$ 次,有几种走法。

分析

考虑 DP。$DP(i, j)$ 表示走了 $i$ 次,当前位于第 $j$ 层的方案数。先计算出能走到楼层 $j$ 的范围,因此有递推式子。

$$ DP(i + 1, j) = \sum_{k=1}^n [|k - j| < |k - b|] DP(i, k) $$

不难发现 $k$ 是一个关于 $j,b$ 的连续区间,可以进行前缀和优化。

const int P = 1E9 + 7;

int main() {
	int n, a, b, k;
	cin >> n >> a >> b >> k;
	vector<ll> dp(n + 2);
	dp[a] = 1;
	for (int i = 1; i <= k; i++) {
		vector<ll> sum(n + 2), ndp(n + 2);
		for (int j = 1; j <= n; j++) {
			sum[j] = (sum[j - 1] + dp[j]) % P;
		}
		for (int j = 1; j <= n; j++) {
			if (j < b) {
				ndp[j] = (sum[(b + j - 1) / 2] - dp[j] + P) % P;
			} else if (j > b) {
				ndp[j] = (sum[n] - sum[(j + b) / 2] - dp[j] + P * 2) % P;
			}
		}
		dp = ndp;
	}
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += dp[i];
	}
	cout << ans % P;
	return 0;
}

G - CF781C

使用 $\left\lceil \frac{2n}{k} \right\rceil$ 个顶点的路径覆盖一个顶点数为 $n$ 的图。

分析

注意到一个顶点数为 $n$ 的树 DFS 经过的点数是 $2n - 1$。

因此我们随便拆掉些边,使之成为一棵树,输出其 DFS 序即可。

const int N = 2E5 + 10;
vector<int> G[N];

int main() {
	int n, m, k;
	cin >> n >> m >> k;

	for (int i = 0; i < m; i++) {
		int u, v;
		cin >> u >> v;
		G[u].push_back(v);
		G[v].push_back(u);
	}

	vector<int> dfn, vis(n + 1);

	std::function<void(int, int)> dfs = [&](int u, int fa) {
		vis[u] = true;
		dfn.push_back(u);
		for (int v : G[u]) {
			if (v != fa && not vis[v]) {
				dfs(v, u);
				dfn.push_back(u);
			}
		}
	};

	dfs(1, 0);

	int kn = dfn.size() / k, ki = dfn.size() % k, u = 0;
	for (int i = 0; i < k; i++) {
		int tkn = kn + (i < ki);
		cout << tkn << ' ';
		for (int j = 0; j < tkn; j++) {
			cout << dfn[u++] << " \n"[j == tkn - 1];
		}
	}

	return 0;
}

H - CF121E

给定一列数,有两种操作:

  • 区间 $[l, r]$ 增加 $d$。
  • 计算 $[l, r]$ 中的 lucky 数。

分析

关键在于 $d > 0$ 且保证所有数字不超过 $10^4$,这使得真实的复杂度并不像想象中的那样高。

本题时限较大,树状数组可以直接通过。

bool lucky(int x) {
	while (x > 0) {
		if (x % 10 != 4 && x % 10 != 7) {
			return false;
		}
		x /= 10;
	}
	return true;
}

template <class T>
struct fwtree; // 树状数组

const int N = 1E4 + 5;

int main() {
	vector<int> luck(N);
	for (int i = 1; i < N; i++) {
		luck[i] = lucky(i);
	}

	int n, m;
	cin >> n >> m;
	vector<int> a(n + 1);
	fwtree<int> tr(n + 1);

	auto add_if = [&](int i, int x) {
		if (x != 0) {
			tr.add(i, x);
		}
	};

	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		add_if(i, luck[a[i]]);
	}

	while (m--) {
		int l, r, x;
		string s;
		cin >> s >> l >> r;
		if (s == "count") {
			cout << tr.sum(r) - tr.sum(l - 1) << endl;
		} else {
			cin >> x;
			for (int i = l; i <= r; i++) {
				add_if(i, luck[a[i] + x] - luck[a[i]]);
				a[i] += x;
			}
		}
	}

	return 0;
}

实际上存在着一种较为复杂的线段树做法。