复习一下。

A. 串

计算长度不超过 $n$ 且蕴含 us 作为子串的字符串有多少个。

分析

读错题目了,还以为是容斥。对 “出现 us”、“出现 u”、“其他” 三种情况分别 DP。

int main() {
    int n;
    cin >> n;
    ll ans = 0;
    ll dp0 = 1, dpu = 0, dps = 0;
    for (int i = 1; i <= n; i++) {
        ll t0 = dp0 * 25 % P;
        ll tu = (dpu * 25 + dp0) % P;
        ll ts = (dpu + dps * 26) % P;
        dp0 = t0, dpu = tu, dps = ts;
        ans = (ans + dps) % P;
    }
    cout << ans;
    return 0;
}

全部代码请看:Nowcoder/NC9981/A.cpp

实际上计算可以做到 $\log n$。记三种情况分别为 $o_n, u_n, s_n$,可以解得

$$ o_n = 25^n, \quad u_n = n 25^{n-1}, \quad s_n = 26^n - o_n-u_n $$

因此计算得

$$ \sum_{i=2}^n s_i = \frac{1}{14400}(576 \cdot 26^{n+1} - (599+24n)25^{n+1}-1) $$

B. 括号

构造长度不超过 $10^5$ 的括号串,使得恰好包含 $k$ 个括号对。

分析

考虑弄 $5 \times 10^4$ 个 (,再考虑如何把 $k$ 拆分成 $k_i$,在 $k_i$ 后加上右括号即可。

拆分可以从 $5\times10^4$ 开始,逐渐递减。若是从小到大,长度会超。

int main() {
    ll k;
    cin >> k;
    vector<int> vis(N + 1);
    for (int i = N; i >= 1; i--) {
        if (k >= i) {
            k -= i;
            vis[i] = 1;
        }
    }
    string s;
    for (int i = 1; i <= N; i++) {
        s.push_back('(');
        if (vis[i])
            s.push_back(')');
    }
    cout << s;
	return 0;
}

全部代码请看:Nowcoder/NC9981/B.cpp

C. 红和蓝

给定一棵 $n$ 个节点的树,需要用红色和蓝色把树染色:

  • 每个红点周围有且仅有一个红点,每个蓝点周围有且仅有一个蓝点。

分析

显然颜色是以二元对的形式分组。下证这样的分组形式唯一。

  • 如果该点是叶子,其必须和父节点同色,形式唯一。
  • 如果该点不是叶子,则只有两种可能:
    • 已经被子节点标记,形式唯一。
    • 未被标记,依 DFS 序知子节点全是已经标记,且确定唯一的。故只能和父节点同色。

对于每个叶子,其必须和其父节点同色,从而可以赋给他们相同的编号。如果某个叶子其父节点已经被标记,则说明此树不可染色。

const int N = 1E5;

vector<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);
		G[v].push_back(u);
	}

	vector<int> vis(n + 1), col(n + 1);
	int cnt = 0;
	std::function<void(int, int)> dfs1 = [&](int x, int fa) {
		for (auto u : G[x]) {
			if (u != fa)
				dfs1(u, x);
		}
		if (!vis[x]) {
			if (vis[fa]) {
				cout << "-1";
				exit(0);
			}
			vis[fa] = vis[x] = ++cnt;
		}
	};

	std::function<void(int, int)> dfs2 = [&](int x, int fa) {
		for (auto u : G[x]) {
			if (u == fa)
				continue;
			if (vis[u] == vis[x]) {
				col[u] = col[x];
			} else {
				col[u] = !col[x];
			}
			dfs2(u, x);
		}
	};

	vis[0] = 1, col[1] = 1;
	dfs1(1, 0), dfs2(1, 0);

	for (int i = 1; i <= n; i++) {
		cout << (col[i] ? 'R' : 'B');
	}

	return 0;
}

全部代码请看:Nowcoder/NC9981/C.cpp

D. 点一成零

给定一个 $n \times n$ 的 01 方阵,每次修改可以把一处格子由 $0$ 改为 $1$。

然后回答询问:每次点击可以把一个 $1$ 的连通块变为 $0$,问有几种操作能够全部为 $0$。

分析

若方阵中有 $a$ 个连通块,每个连通块的大小是 $b_i$,那么答案是

$$ ans = a! \prod b_i $$

因此我们需要维护连通块的个数,和每个连通块的大小。用带权并查集实现即可。

同时需要维护连通块的乘积,在合并时维护乘积。

const int N = 500 + 10;
char ss[N][N];

int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> ss[i];
	}
	pre_all(n * n + 10);

	int cnt = 0, mul = 1;
	DSU dsu(n * n);

	auto merge = [&](int x1, int y1, int x2, int y2) {
		if (x2 < 0 || x2 >= n || y2 < 0 || y2 >= n)
			return;
		if (ss[x2][y2] == '0')
			return;
		int p1 = x1 * n + y1, p2 = x2 * n + y2;
		if (dsu.find(p1) != dsu.find(p2)) {
			mul = 1ll * mul * Inv[dsu.size(p1)] % P * Inv[dsu.size(p2)] % P;
			dsu.merge(p1, p2);
			mul = 1ll * mul * dsu.size(p1) % P;
			cnt--;
		}
	};

	auto change = [&](int i, int j) {
		if (ss[i][j] == '1')
			return;
		ss[i][j] = '1';
		cnt++;
		merge(i, j, i, j - 1);
		merge(i, j, i, j + 1);
		merge(i, j, i - 1, j);
		merge(i, j, i + 1, j);
	};

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (ss[i][j] == '0')
				continue;
			ss[i][j] = '0';
			change(i, j);
		}
	}

	int Q;
	cin >> Q;
	while (Q--) {
		int x, y;
		cin >> x >> y;
		change(x, y);
		cout << 1ll * mul * fac[cnt] % P << "\n";
	}

	return 0;
}

全部代码请看:Nowcoder/NC9981/D.cpp

E. 三棱锥之刻

计算几何,不讲。

const double PI = acos(-1.0);

int main() {
	double a, r;
	cin >> a >> r;
	double a2 = a * a, r2 = r * r;
	double s2 = r2 - a2 / 24;
	double ans = 0;
	if (s2 > 0) {
		double s = sqrt(s2);
		if (s2 < a2 / 12) {
			ans = PI * s2;
		} else if (s2 < a2 / 3) {
			double costh = a / s / 2 / sqrt(3);
			double th = acos(costh);
			double sinth = sqrt(1 - costh * costh);
			double t = s * sinth;
			ans = t * a / sqrt(12) + (PI / 3 - th) * s2;
			ans *= 3;
		} else {
			ans = a  * a * sqrt(3) / 4;
		}
	}
	printf("%.10lf", ans * 4);
	return 0;
}

全部代码请看:Nowcoder/NC9981/E.cpp

F. 对答案一时爽

给定 $n$ 道选择题,以及牛牛和牛妹的选择。

问两人得分之和有可能达到的最大值和最小值。

分析

显然最小值是 $0$。

当两人答案相同时,可能得两分;否则得一分。

int main() {
    int n;
    cin >> n;
    int sum = 0;
    vector<char> s1(n), s2(n);
    for (auto &si : s1)
        cin >> si;
    for (auto &si : s2)
        cin >> si;
     
    for (int i = 0; i < n; i++) {
        if (s1[i] == s2[i]) {
            sum += 2;
        } else {
            sum += 1;
        }
    }
    cout << sum << " " << 0;
	return 0;
}

全部代码请看:Nowcoder/NC9981/F.cpp

H. 幂塔个位数的计算

计算 $a \uparrow \uparrow n$ 的个位数。

分析

去年我是打表找规律,但是很容易误认为解只和 $a \bmod 10$ 有关。

更朴素的想法是欧拉降幂。

a = int(input())
n = int(input())
if(n == 1):
    print(a % 10)
elif(n == 2):
    print(pow(a, a % 4 + 4, 10))
else:
    x = pow(a, a % 2 + 2, 4)
    print(pow(a, x + 4, 10))

全部代码请看:Nowcoder/NC9981/H.py

还有些题解误认为 $a, n$ 都对 $100$ 取模,这里给出一个 Hack。

INPUT:  2 1001
==============
OUTPUT: 6

I. 限制不互素对的排列

构造一个长为 $n$ 的排列,使得其中恰有 $k$ 对相邻的数,其 $\gcd(a_i, a_{i+1}) > 1$。

分析

怎么感觉这题见过。

注意到偶数序列是两两 $\gcd \geqslant 2$ 的,而相邻整数一定有 $\gcd(i, i+1) = 1$。

令 $1$ 作为奇偶分界点,对于 $k < n / 2$ 我们已经有了构造方法。

对于 $k = n / 2$,需要额外再找出一对。我找的是 $(6, 3)$。

int main() {
	int n, k;
	cin >> n >> k;
	if (n < 6 && k == n / 2) {
		cout << -1;
		exit(0);
	}
	vector<int> vis(n + 1), ans;
	for (int i = 2; i <= n; i += 2) {
		if (i == 6)
			continue;
		ans.push_back(i);
	}
	ans.push_back(6);
	ans.push_back(3);
	for (int i = 0; i <= k; i++) {
		cout << ans[i] << " ";
		vis[ans[i]] = true;
	}
	for (int i = 1; i <= n; i++) {
		if (!vis[i])
			cout << i << " ";
	}
	return 0;
}

全部代码请看:Nowcoder/NC9981/I.cpp

J. 一群小青蛙呱蹦呱蹦呱

给定一个集合 $A = {1, \cdots, n}$,在其中划去所有质数的幂次 $p_j^k$,组成集合 $B$。问集合 $B$ 所有数的 $\lcm$ 是多少。

分析

注意到 $\lcm$ 再取模,只可能是对于所有因子独立计算贡献。

int main() {
	ll n;
	cin >> n;
	if (n <= 5) {
		cout << "empty";
		return 0;
	}
	Euler(n / 2);
	ll ans = 1;
	for (auto pj : primes) {
		ll tn = n;
		if (pj == 2) {
			tn /= 3;
		} else {
			tn /= 2;
		}
		tn /= pj;
		while (tn > 0) {
			tn /= pj;
			ans = ans * pj % P;
		}
	}
	cout << ans;
	return 0;
}

全部代码请看:Nowcoder/NC9981/J.cpp