A - CF515A

判断一下是否恰好用 $k$ 步能从 $(0,0)$ 点走到 $(a, b)$ 点。

分析

判断一下步数是否足够走到,且剩余的步数的奇偶性即可。

int main() {
	ll a, b, s;
	cin >> a >> b >> s;
    a = abs(a), b = abs(b);
	if (a + b <= s && (s - a - b) % 2 == 0) {
		cout << "Yes";
	} else {
		cout << "No";
	}
	return 0;
}

B - CF430B

“祖玛游戏”,问最多能消除多少个球。

分析

暴力枚举在哪个位置插入球,计算答案,维护全局最大值即可。

可以使用双指针,也可以借助 vector。

int check(vector<int> v) {
	int cnt = 1;
	auto end = v.end();
	for (int i = 1; i < v.size(); i++) {
		if (v[i] == v[i - 1]) {
			cnt++;
		} else {
			if (cnt >= 3) {
				end = v.begin() + i;
				break;
			}
			cnt = 1;
		}
	}
	if (cnt >= 3) {
		v.erase(end - cnt, end);
        return check(v);
	} else {
		return v.size();
	}
}

int main() {
	int n, k, x;
	cin >> n >> k >> x;
	vector<int> c(n);
	for (auto &ci : c) {
		cin >> ci;
	}
	int ans = 1E9;
	for (int i = 0; i <= n; i++) {
		auto c2 = c;
		c2.insert(c2.begin() + i, x);
		ans = min(ans, check(c2));
	}
	cout << max(0, n - ans);

	return 0;
}

C - CF515C

题目大意

给定一个数 $x$,构造一个尽可能大且不含 $0,1$ 的数 $x’$ 使得 $F(x) = F(x’)$。

其中 $F(x)$ 定义为 $x$ 的各位阶乘之积。

分析

不难发现,要使得构造的数尽可能大,我们要将原先位上的数字拆分成尽可能多的数。我们预处理出所有 2 ~ 9 的数的最优表达,然后对原先的各个位上的数进行替换,最后排序即可。

int main() {
	std::map<char, string> mp;
	mp['0'] = mp['1'] = "";
	// 2! = 2
	mp['2'] = "2";
	// 3! = 6
	mp['3'] = "3";
	// 4! = 24 = 3! 2! 2!
	mp['4'] = mp['3'] + "22";
	// 5! = 120
	mp['5'] = "5";
	// 6! = 720 = 5! 3!
	mp['6'] = mp['5'] + mp['3'];
	// 7! = 5040
	mp['7'] = "7";
	// 8! = 40320 = 7! 2! 2! 2!
	mp['8'] = mp['7'] + "222";
	// 9! = 362880 = 7! 3! 3! 2!
	mp['9'] = mp['7'] + mp['3'] + mp['3'] + "2";
	int n;
	string s, ans;
	cin >> n >> s;
	for (auto ci : s) {
		ans += mp[ci];
	}
	sort(ans.begin(), ans.end(), std::greater<char>());
	cout << ans;
	return 0;
}

D - CF182D

题目大意

给出字符串的因子的定义:如果一个字符串A可以被表示为若干个字符串B首尾相连拼接,则称 B 是 A 的一个因子。给出两个字符串,问这两个字符串有多少个因子。

分析 | By ZHL

我们暴力枚举任意一个字符串的前缀,然后用这段前缀分别去检验是否为两个字符串的因子,最后统计答案即可。检验的时候需要用字符串哈希优化复杂度。

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1e6+7;
typedef unsigned long long ULL;
const int P = 13331;
ULL h[N], h2[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
string s1, s2;
int n1, n2;
bool check (int len){
    ULL temp = h[len], now = temp;
    if (n1 % len || n2 % len) return 0;
    int step = n1 / len - 1;
    while (step --){
        now = now * p[len] + temp;
    }
    if (now != h[n1]) return 0;
    now = temp;
    step = n2 / len - 1;
    while (step --){
        now = now * p[len] + temp;
    }
    return now == h2[n2];
}
int main (){
    IOS
    cin >> s1 >> s2;
    n1 = s1.size (), n2 = s2.size (); 
    s1 = " " + s1, s2 = " " + s2;
    p[0] = 1;
    for (int i = 1 ; i <= n1; i ++ ){
        h[i] = h[i - 1] * P + s1[i];
        p[i] = p[i - 1] * P;
    }
    for (int i = 1 ; i <= n2 ; i ++){
        h2[i] = h2[i - 1] * P + s2[i];
    }
    int ans = 0;
    for (int i = 1 ; i <= n1 ; i ++){
        ans += check (i);
    }
    cout << ans << endl;
    return 0;
}

E - CF233C

构造三元环个数恰为 $k$ 的图,并且点数不得超过 $100$。

分析

注意到对于 $x$ 点的完全图,加入一个点并连接 $y(y \leqslant x)$ 条边,此时增加的三元环数量为 $y(y-1)/2$,即每加一条边就增加 $y-1$ 个三元环。

因此不断加边即可。当加到完全图时,重新加点即可。显然在 $10^5$ 内该算法都有解。

const int N = 200;
int A[N][N];
 
int main() {
	int n;
	cin >> n;
	int tn = 0, mx = 0;
	for (int i = 2; i <= 100; i++) {
		A[i][1] = A[1][i] = 1;
		for (int j = 2; j < i; j++) {
			if (tn + j - 1 > n) {
				break;
			}
			mx = i;
			tn += j - 1;
			A[i][j] = A[j][i] = 1;
		}
	}
	cout << mx << endl;
	for (int i = 1; i <= mx; i++) {
		for (int j = 1; j <= mx; j++) {
			cout << A[i][j];
		}
		cout << endl;
	}
	return 0;
}

F - CF454C

给定 $m$ 面的骰子,问扔 $n$ 次后的期望最大值是多少。

分析

类似容斥的思想,假设扔 $n$ 次骰子点数皆不超过 $x$ 的概率是 $\left(\frac{x}{m}\right)^n$,那么恰好最大值是 $x$ 的概率是

$$ \left(\frac{x}{m}\right)^n - \left(\frac{x - 1}{m}\right)^n $$

因此期望是

$$ E = \sum_{i=1}^m \left( \frac{i^n}{m^n} - \frac{(i - 1)^n}{m^n} \right) i $$

int main() {
	int m, n;
	cin >> m >> n;
	double ans = 0, p = 0;
	for (int i = 1; i <= m; i++) {
		double tp = pow(i * 1.0 / m, n);
		ans += (tp - p) * i;
		p = tp;
	}
	printf("%.12lf", ans);
	return 0;
}

G - CF109C

给定一个 $n$ 个节点的树,求三元对 $(i, j, k)$ 的个数,其中 $i \to j$ 和 $i \to k$ 的简单路径上存在 lucky 的边。

分析

我们可以转换视角,把 lucky 边看作分割条件,在树上划分联通块。$i \to j$ 的简单路径上存在 lucky 边,即等价于 $i$ 和 $j$ 处于不同的联通块。

可以使用并查集处理。

bool lucky(int n);

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

struct DSU; // 并查集

int main() {
	int n;
	cin >> n;

	DSU dsu(n + 1);

	for (int i = 0; i < n - 1; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		w = lucky(w);
		if (!w) {
			dsu.merge(u, v);
		}
		G[u].push_back(v);
		G[v].push_back(u);
	}

	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		if (dsu.find(i) != i) {
			continue;
		}
		int other = n - dsu.size(i);
		ans += 1ll * dsu.size(i) * other * (other - 1);
	}

	cout << ans;

	return 0;
}

也可以直接 DFS 处理。

bool lucky(int n);

const int N = 1E5 + 5;

using pib = pair<int, bool>;
vector<pib> G[N];

int main() {
	int n;
	cin >> n;

	for (int i = 0; i < n - 1; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		w = lucky(w);
		G[u].push_back({v, w});
		G[v].push_back({u, w});
	}

	vector<int> r;
	std::function<int(int, int)> dfs = [&](int u, int fa) {
		int sum = 0;
		for (auto [v, w] : G[u]) {
			if (v != fa) {
				int x = dfs(v, u);
				if (w) {
					r.push_back(x);
				} else {
					sum += x;
				}
			}
		}
		return sum + 1;
	};

	r.push_back(dfs(1, -1));

	ll ans = 0;
	for (auto sz : r) {
		int other = n - sz;
		ans += 1ll * sz * other * (other - 1);
	}

	cout << ans;

	return 0;
}

H - CF454D

给定数列 $\{a_i\}$,求 GCD 为 $1$ 的序列 $\{b_i\}$,并且

$$ \sum_{i=1}^n |a_i - b_i| $$

尽可能小。

分析

注意到 $a_i \leqslant 30$,这说明 $b_i \leqslant 60$。而 $60$ 之内只有 $16$ 个质数,我们可以使用位运算来表示。

因此判断互质只需要使用按位或即可。至此可以直接使用 DFS 通过本题。

using pii = pair<int, int>;
const int N = 60, NP = 16;

int fac_mask[N];
const int p[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};

void pre() {
	for (int i = 2; i < N; i++) {
		for (int j = 0; j < NP; j++) {
			if (i % p[j] == 0) {
				fac_mask[i] |= 1 << j;
			}
		}
	}
}

int main() {
	pre();
	int n;
	cin >> n;
	vector<pii> a(n);
	for (int i = 0; i < n; i++) {
		cin >> a[i].first;
		a[i].second = i;
	}

	sort(a.begin(), a.end(), std::greater<pii>());
	vector<int> b(n), ans;
	int min_cost = 1E9, now_cost = 0, mask = 0;

	std::function<void(int, int)> dfs = [&](int i, int past) {
		if (now_cost >= min_cost) {
			return;
		}
		if (i == n) {
			min_cost = now_cost;
			ans = b;
			return;
		}
		for (int j = 1; j <= past; j++) {
			if (mask & fac_mask[j]) {
				continue;
			}
			b[i] = j;
			now_cost += std::abs(a[i].first - j);
			mask ^= fac_mask[j];
			dfs(i + 1, j);
			now_cost -= std::abs(a[i].first - j);
			mask ^= fac_mask[j];
		}
	};
	dfs(0, N - 1);

	// cout << min_cost << endl;
	vector<int> out(n);
	for (int i = 0; i < n; i++) {
		out[a[i].second] = ans[i];
	}
	for (auto oi : out) {
		cout << oi << " ";
	}

	return 0;
}

可以用 DP 继续进行优化。