题意:
给定三个长度为 n n n 的数组 a , b , c a,b,c a,b,c , 1 ≤ a i , b i , c i ≤ n 1\leq a_i,b_i,c_i\leq n 1≤ai,bi,ci≤n。两个人 A l i c e Alice Alice 和 B o b Bob Bob 分别拿数 n n n 次,第 i i i 次拿数时,两人从 { a i , b i , c i } \{a_i,b_i,c_i\} {ai,bi,ci} 三个数中选择两个,每次拿数 A l i c e Alice Alice 先手选择一个,拿完后 B o b Bob Bob 从剩下的两个数中选择一个。问是否有 A l i c e Alice Alice 的一种拿数选择,使得拿完后 B o b Bob Bob 的数恰好是一个 1 − n 1-n 1−n 的排列。
数据范围: 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1≤n≤105
题解:
首先,在每个位置 A l i c e Alice Alice 拿完数后,至少有一个位置使得 B o b Bob Bob 有两种选择(即剩下的两个数不相同),这样, B o b Bob Bob 必然就可以有一种选择使得自己拿到的 n n n 个数构不成排列。
证明:假设存在一种选择方式使得 B o b Bob Bob 拿到的 n n n 个数为一个排列。因为 B o b Bob Bob 至少存在一个位置两个数不同,故找到这个位置,将数字换成另一个,此时 B o b Bob Bob 选择的 n n n 个数就构不成排列了。
因此, A l i c e Alice Alice 拿完数后,每个位置剩下的数都得是两个相同的数 x i x_i xi,且 x i x_i xi 各不相同,则 B o b Bob Bob 选择的 n n n 个数就是一个排列了,否则 B o b Bob Bob 选择的 n n n 个数至少有一种选择使得其不为排列。
代码:
#include
using namespace std;
void solve() {
int n;
cin >> n;
vector<int> a(n), b(n), c(n);
for (int i = 0; i < n; ++i) cin >> a[i], a[i] -= 1;
for (int i = 0; i < n; ++i) cin >> b[i], b[i] -= 1;
for (int i = 0; i < n; ++i) cin >> c[i], c[i] -= 1;
vector<int> cnt(n);
bool ok = true;
for (int i = 0; i < n; ++i) {
vector<int> vec = {a[i], b[i], c[i]};
sort(vec.begin(), vec.end());
if (vec[1] != vec[0] && vec[1] != vec[2]) {
ok = false;
break;
} else {
cnt[vec[1]] += 1;
}
}
if (ok) {
for (int i = 0; i < n; ++i) {
if (cnt[i] == 0) {
ok = false;
break;
}
}
}
if (ok) cout << "YES\n";
else cout << "NO\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}
题意:
给定两个长度为 n n n 的数组 a , b a,b a,b , 1 ≤ a i , b i ≤ n 1\leq a_i,b_i\leq n 1≤ai,bi≤n。需要构造一个长度为 n n n 的数组 c c c, 1 ≤ c i ≤ n 1\leq c_i\leq n 1≤ci≤n。两个人 A l i c e Alice Alice 和 B o b Bob Bob 分别拿数 n n n 次,第 i i i 次拿数时,两人从 { a i , b i , c i } \{a_i,b_i,c_i\} {ai,bi,ci} 三个数中选择两个,每次拿数 A l i c e Alice Alice 先手选择一个,拿完后 B o b Bob Bob 从剩下的两个数中选择一个。问是否有 A l i c e Alice Alice 的一种拿数选择,使得拿完后 B o b Bob Bob 的数恰好是一个 1 − n 1-n 1−n 的排列。
数据范围: 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1≤n≤105
题解:
相较于子问题1,子问题2需要构造一个 c c c,满足 B o b Bob Bob 拿的数恰好是一个 1 − n 1-n 1−n 的排列。
可以知道的是,如果 a i = b i a_i=b_i ai=bi,则 c i c_i ci 为什么数都无所谓了。
如果存在 a i = b i = a j = b j , i ≠ j a_i=b_i=a_j=b_j,i\neq j ai=bi=aj=bj,i=j,则无论怎么构造 c c c, B o b Bob Bob 都有至少一种选择方式使得拿到的数构不成一个排列。
故我们现在面对的局面是,有 k k k 个 a i = b i a_i=b_i ai=bi,且这 k k k 个 a i a_i ai 各不相同,其中 0 ≤ k ≤ n 0\leq k\leq n 0≤k≤n。
对于剩下的 n − k n-k n−k 个位置,其需要填充剩下的数,这里剩下的数是指除了 k k k 个 a i a_i ai 以外其他的数。
故我们需要思考的是如何在 n − k n-k n−k 个位置填充剩下的 n − k n-k n−k 个数,使得我们选择的一定是一个排列?
将问题转换为图论,对于 a i , b i a_i,b_i ai,bi,考虑在 a i a_i ai 和 b i b_i bi 之间连一条边。要确定这条边的方向,如果是从 a i a_i ai 指向 b i b_i bi ,表示选择 b i b_i bi,否则表示选择 a i a_i ai,我们需要对每条边都选择一个方向,使得每个点的入度都恰好为 1 1 1,对于 a i = b i a_i=b_i ai=bi 的情况也可以一并操作。
由于一共 n n n 个点, n n n 条边,可能构成 m m m 个连通分量。对于每个连通分量,其中的点数和边数必须相同,否则就没办法使得每个点的入度都为 1 1 1 了。
故只需要建完图后,求每个连通分量的点数和边数是否相同即可。
即我们构造的 c c c 满足 c i = a i c_i=a_i ci=ai 或者 c i = b i c_i=b_i ci=bi 即可。特殊地,对于 a i = b i a_i=b_i ai=bi 时, c i ∈ [ 1 , n ] c_i\in[1,n] ci∈[1,n]。
代码:
#include
using namespace std;
constexpr int MOD = 998244353;
void solve() {
int n;
cin >> n;
vector<int> a(n), b(n);
for (int i = 0; i < n; ++i) cin >> a[i], a[i] -= 1;
for (int i = 0; i < n; ++i) cin >> b[i], b[i] -= 1;
vector<int> p(n), points(n, 1), edges(n, 0);
iota(p.begin(), p.end(), 0);
auto getp = [&](int x) {
int root = x;
while (root != p[root]) root = p[root];
while (x != p[x]) {
int nx = p[x];
p[x] = root;
x = nx;
}
return root;
};
for (int i = 0; i < n; ++i) {
int pa = getp(a[i]), pb = getp(b[i]);
if (pa != pb) {
points[pb] += points[pa];
points[pa] = 0;
edges[pb] += edges[pa];
edges[pa] = 0;
p[pa] = pb;
} else {
edges[pb] += 1;
}
}
bool ok = true;
for (int i = 0; i < n; ++i)
if (i == getp(i)) {
if (edges[i] != points[i]) {
ok = false;
break;
}
}
if (ok) cout << "YES\n";
else cout << "NO\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}
题意:
给定两个长度为 n n n 的数组 a , b a,b a,b , 1 ≤ a i , b i ≤ n 1\leq a_i,b_i\leq n 1≤ai,bi≤n。需要构造一个长度为 n n n 的数组 c c c, 1 ≤ c i ≤ n 1\leq c_i\leq n 1≤ci≤n。两个人 A l i c e Alice Alice 和 B o b Bob Bob 分别拿数 n n n 次,第 i i i 次拿数时,两人从 { a i , b i , c i } \{a_i,b_i,c_i\} {ai,bi,ci} 三个数中选择两个,每次拿数 A l i c e Alice Alice 先手选择一个,拿完后 B o b Bob Bob 从剩下的两个数中选择一个。问:有多少种构造 c c c 的方式,使得有 A l i c e Alice Alice 的一种拿数选择,使得拿完后 B o b Bob Bob 的数恰好是一个 1 − n 1-n 1−n 的排列。
数据范围: 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1≤n≤105
题解:
在子问题 2 的基础上继续考虑。
对于每个连通分量,点数和边数相同。可以看成是一棵树加上一条边。
代码:
#include
using namespace std;
constexpr int MOD = 998244353;
void solve() {
int n;
cin >> n;
vector<int> a(n), b(n);
for (int i = 0; i < n; ++i) cin >> a[i], a[i] -= 1;
for (int i = 0; i < n; ++i) cin >> b[i], b[i] -= 1;
vector<int> p(n), points(n, 1), edges(n, 0);
iota(p.begin(), p.end(), 0);
auto getp = [&](int x) {
int root = x;
while (root != p[root]) root = p[root];
while (x != p[x]) {
int nx = p[x];
p[x] = root;
x = nx;
}
return root;
};
vector<int> self_loop(n);
for (int i = 0; i < n; ++i) {
int pa = getp(a[i]), pb = getp(b[i]);
if (pa != pb) {
points[pb] += points[pa];
points[pa] = 0;
edges[pb] += edges[pa] + 1;
edges[pa] = 0;
self_loop[pb] += self_loop[pa];
self_loop[pa] = 0;
p[pa] = pb;
} else {
edges[pb] += 1;
}
if (a[i] == b[i]) self_loop[pb] += 1;
}
bool ok = true;
int ans = 1;
for (int i = 0; i < n; ++i)
if (i == getp(i)) {
if (edges[i] != points[i]) {
ok = false;
break;
}
if (self_loop[i] == 0) ans = ans * 2 % MOD;
else ans = 1ll * ans * n % MOD;
}
if (!ok) {
cout << "0\n";
return;
} else {
cout << ans << "\n";
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}