比赛总结:MX-J10&X8
赛时得分:\((100 + 100 + 100 + 100 + 18 + 0 + 0 + 0) / 800\)
T0
按照题意模拟即可。
T1
Tag:贪心
令 \(b_i=x-a_i\),求 \(b\) 的最大字段和 \(x\),答案为 \((\sum a) + x\)。
T2
Tag:构造
任何操作都可以等价拆解成形如 \((1,1)\) 的操作,那么对于每个 \(1\le i<n\),操作 \(a_i\) 和 \(a_{i+1}\),直到 \(a_i=b_i\)。最后判断 \(a_n\) 是否等于 \(b_n\) 即可。
T3
Tag:构造,DSU
假设当前快乐值为 \(a\),走过的路径长度为 \(d\)。之后再走了 \(b\) 长度的路径,能量值为 \(b\),那么若 \(a\) 和 \(d\) 奇偶性相同,显然 \((d+b)\) 和 \((a\oplus b)\) 的奇偶性相同。所以根据数学归纳法(边界从 \(a\) 和 \(d\) 都是 \(0\) 开始),我们最终得到的 \(a\) 和路径长度的奇偶性相同,那么只需要判断 \(s\) 到 \(t\) 是否存在一条长度的奇偶性与 \(x\) 相同的路径即可。
若存在,我们可以先走到 \(t\),假设当前快乐值是 \(a\),然后沿着任意一条边来回走 \((x\oplus a)\) 次,即可。若不存在,根据上面的结论,显然构造不出答案。当然需要特判 \(s=t\) 的情况。
如何判断是否存在这样的路径?首先用一个并查集判掉 \(s\) 和 \(t\) 不连通的情况。然后用另一个并查集,将每个点拆成 \(odd_i\) 和 \(even_i\),对于原图的每条边 \((u,v)\),将 \(odd_u\) 和 \(even_v\) 连通,将 \(even_u\) 与 \(odd_v\) 连通,即可。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 2e5 + 5;
int n, m, q;
struct DSU {
int n;
vector<int> f, siz;
void init(int _n) {
n = _n;
f.assign(n + 5, 0);
siz.assign(n + 5, 1);
for (int i = 1; i <= n; i++) f[i] = i;
}
int find(int x) {
return (f[x] == x ? x : f[x] = find(f[x])); // 路径压缩
}
void merge(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) return;
siz[fy] += siz[fx];
f[fx] = fy;
}
bool is_merge(int x, int y) {
return (find(x) == find(y));
}
} dsu, dsu2;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> q;
dsu.init(n);
dsu2.init(n * 2); // u:odd, n+u:even
int u, v;
for (int i = 1; i <= m; i++) {
cin >> u >> v;
dsu.merge(u, v);
dsu2.merge(n + u, v);
dsu2.merge(u, n + v);
}
int s, t, x;
while (q--) {
cin >> s >> t >> x;
if (!dsu.is_merge(s, t)) {
cout << "expand\n";
} else if (x & 1) {
if (dsu2.is_merge(s, n + t)) {
cout << "tribool\n";
} else {
cout << "expand\n";
}
} else {
if (s == t) {
if (x == 0) {
cout << "tribool\n";
} else if (dsu.siz[dsu.find(s)] > 1) {
cout << "tribool\n";
} else {
cout << "expand\n";
}
} else if (dsu2.is_merge(s, t)) {
cout << "tribool\n";
} else {
cout << "expand\n";
}
}
}
return 0;
}
T4
Tag:计数,构造
首先考虑一个问题:给定一个有根树,对于每个点 \(u\),给定儿子个数 \(a_u\) 以及每个儿子的子树大小 \(b_{u,j}\),求方案数。
记 \(cnt_{u,j}\) 表示 \(b_{u,i}=j\) 的 \(i\) 的个数。记 \(siz_u\) 表示 \(u\) 的子树大小,记 \(tot_{j}\) 表示 \(siz_i = j\) 的 \(i\) 的个数。
那么答案为 \(\prod\limits_{i=1}^{n} \prod\limits_j \binom{tot_j - \sum\limits_{k=1}^{i-1}cnt_{k,j}}{cnt_{i,j}}\)。
对于这道题目,我们可以让一个重心为根,因为重心连向的每个连通块大小不超过 \(n/2\),所以可以确定重心。然后对于其他点,删去 \((\ge n/2)\) 的那个连通块的信息,就可以转化为上面的问题。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 3e5 + 5, MOD = 998244353, INF = 1e9;
int n, a[N], root, siz[N], cnt[N];
int fact[N], inv[N];
vector<int> b[N];
int fpow(int a, int b) {
int ans = 1;
for (; b; b >>= 1, a = 1ll * a * a % MOD) {
if (b & 1) ans = 1ll * ans * a % MOD;
}
return ans;
}
int C(int n, int m) {
if (n < m) return 0;
return 1ll * fact[n] * inv[m] % MOD * inv[n - m] % MOD;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
fact[0] = 1;
for (int i = 1; i <= n; i++) fact[i] = 1ll * fact[i - 1] * i % MOD;
inv[n] = fpow(fact[n], MOD - 2);
for (int i = n - 1; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % MOD;
for (int i = 1; i <= n; i++) {
cin >> a[i];
int x;
for (int j = 1; j <= a[i]; j++) {
cin >> x;
b[i].push_back(x);
}
sort(b[i].begin(), b[i].end());
if (b[i][a[i] - 1] <= n / 2) root = i;
}
for (int i = 1; i <= n; i++) {
if (i != root && b[i][a[i] - 1] >= n / 2) {
b[i].pop_back();
a[i]--;
}
for (int j : b[i]) {
cnt[j]++;
}
}
int ans = 1;
for (int i = 1; i <= n; i++) {
int x = 0;
for (int j = 0; j < a[i]; j++) {
x++;
if (j == a[i] - 1 || b[i][j] != b[i][j + 1]) {
ans = 1ll * ans * C(cnt[b[i][j]], x) % MOD;
cnt[b[i][j]] -= x;
x = 0;
}
}
}
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号