Good Bye 2022: 2023 is NEAR
C.Koxia and Number Theory(鸽巢原理)
题目大意
给定一个长度为\(n(2 \le n \le 100)\)的数组\((1\le a[i] \le 10^{18})\),问是否存在一个正整数x,使得对于任意的i,j都满足\(gcd(a_i + x,a_j + x) = 1\)。
解题思路
首先,如果有两个\(a_i\)相等的话,答案一定是\(NO\)。
要使\(gcd(i, j) = 1\),其实就是对于任意一个质数\(p\),\(i,j\)都不能同时被它整除。
当\(p\)为\(2\)的时候,我们很容易发现数组\(a\)中不能同时有两个及两个以上的奇数和偶数,因为这个时候不管\(x\)取奇数还是偶数,都会使有两个数都能被2整除。
那当\(p\)为其它质数呢。
假设我们现在有\(a_i\%p=a_j\%p=r\),
这个时候必须要满足\(x\%p \neq p-r\),不然\(a_i,a_j\)就能同时被p整除了。
我们令\(cnt_i\)表示\(i\)在\([a_1\%p,\ a_2\%p,\ \cdots, a_n\%p]\)中出现的次数。
如果\(cnt_i\ge 2\),就说明\(x\%p \neq p-i\),如果所有的\(cnt_i\)都大于等于2的话,\(x\)就不能取任何值了。
现在就剩下我们应该遍历多少个\(p\)的问题了,事实上,当\(p\)很大的时候,是不可能满足所有的\(cnt_i\)都大于等于2的,一共有\(p\)个\(cnt_i\),要想使他们都大于等于2,
至少需要\(2p\)个\(a_i\),然而题目给定的\(n\)是不超过\(100\)的。
参考代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[110];
void work()
{
int n;
cin >> n;
set<int> st;
for (int i = 0; i < n; ++i)
{
cin >> a[i];
st.insert(a[i]);
}
if (st.size() != n)
{
cout << "NO" << endl;
return ;
}
int flag = 1;
for (int i = 2; i <= n / 2; ++i)
{
int cnt[100] = {0};
for (int j = 0; j < n; ++j)
{
++cnt[a[j] % i];
}
if (*min_element(cnt, cnt + i) >= 2)
{
flag = 0;
break;
}
}
cout << (flag ? "YES" : "NO") << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while (T--)
{
work();
}
return 0;
}
D. Koxia and Game(图论,并查集(DSU,Disjoint Set Union),dfs)
题目大意
有三个数组\(a, b, c\),其中\(a, b\)题目给定,所有数组中的元素均处在\([1,\ n]\),有两名玩家\(K,M\),每一回合两名玩家进行如下操作:
- 玩家\(K\)从\(\{a_i,\ b_i,\ c_i\}\)中拿走一个元素。
- 玩家\(M\)从剩下的元素中选择一个。
问有多少个\(c\)数组使得玩家\(M\)拿出的元素必构成一个全排列,所有玩家操作最优。
解题思路
在每一轮中,玩家\(K\)必定要拿走一个元素使得剩下的两个元素相同,不留给玩家\(M\)选择的机会,这就要求\(c_i\)必定等于\(a_i\)或\(b_i\)。
假设在最后一轮中玩家\(M\)有选择的机会,因为这个时候全排列中只剩下一种元素,那么\(M\)必然可以选择另外一个元素使所有元素不构成全排列。
现在既然最后一轮的策略已经确定,即游戏开始后\(M\)在最后一轮中选择的数字是确定的,那么前\(n-1\)轮按照相同的分析方法可得,
玩家\(K\)必定要在每轮中都使得剩下的两个元素相同。
同时我们还可以得出,玩家\(M\)在每轮中选择的数字必然是\(a_i\)或\(b_i\)。我们将\((a_i,\ b_i)\)视为一条边,玩家\(K\)的操作对应着在每一条边中选择两个端点中的一个。要使得最后玩家\(M\)选择的元素为排列,对应着图中每一个节点都被一条边指向(选中)。
为了满足这个要求,在该图的每个联通分量中,边的数目要等与点的数目(基环树)。
剩下就是计数的问题。
由于每个联通分量都是基环树,即联通且仅有一个环。
- 如果这个环是个自环,就是说有一个\(i\)满足\(a_i=b_i\),那么这个\(c_i\)可以取任意值(n种选择),这个环以外的边都选择远离这个环的节点即可。
- 如果不是自环,在这个环上可以逆时针与顺时针选择(2种),剩下的边仍然选择远离这个环的节点。
最后将每个联通分量的结果相乘即可。
参考代码
并查集
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 998244353;
const int N = 1e5 + 10;
int a[N], b[N], n;
int cnt_v[N], cnt_e[N], fa[N], selfloop[N], vis[N];
int getfa(int x)
{
return x == fa[x] ? x : fa[x] = getfa(fa[x]);
}
void init()
{
iota(fa + 1, fa + 1 + n, 1);
fill(cnt_v + 1, cnt_v + 1 + n, 1);
fill(cnt_e + 1, cnt_e + 1 + n, 0);
fill(selfloop + 1, selfloop + 1 + n, 0);
fill(vis + 1, vis + 1 + n, 0);
}
void merge(int u, int v)
{
cnt_v[u] += cnt_v[v];
cnt_e[u] += cnt_e[v];
selfloop[u] |= selfloop[v];
fa[v] = u;
}
void work()
{
cin >> n;
for (int i = 0; i < n; ++i)
cin >> a[i];
for (int i = 0; i < n; ++i)
cin >> b[i];
init();
for (int i = 0; i < n; ++i)
{
int u = getfa(a[i]);
int v = getfa(b[i]);
if (u != v)
merge(u, v);
cnt_e[u] += 1;
if (a[i] == b[i])
selfloop[u] = 1;
}
ll ans = 1;
for (int i = 1; i <= n; ++i)
{
int f = getfa(i);
if (vis[f] == 0)
{
vis[f] = 1;
if (cnt_v[f] != cnt_e[f])
ans = 0;
else
{
ans *= (selfloop[f] == 1 ? n : 2);
ans %= mod;
}
}
}
cout << ans << endl;
}
int main()
{
int T;
cin >> T;
while (T--)
{
work();
}
return 0;
}
DFS
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
const ll mod = 998244353;
int head[N], cnt;
struct
{
int v, next;
}e[2 * N];
int a[N], b[N], vis[N];
int vertex, edge, selfloop;
void add(int u, int v)
{
e[++cnt].next = head[u];
e[cnt].v = v;
head[u] = cnt;
}
void dfs(int u)
{
vis[u] = 1;
++vertex;
int v;
for (int i = head[u]; i; i = e[i].next)
{
v = e[i].v;
++edge;
if (v == u)
{
selfloop = 1;
}
else if (vis[v])
{
continue;
}
else
{
dfs(v);
}
}
}
void work()
{
cnt = 0;
memset(head, 0, sizeof(head));
memset(vis, 0, sizeof(vis));
int n;
cin >> n;
for (int i = 0; i < n; ++i)
{
cin >> a[i];
}
for (int i = 0; i < n; ++i)
{
cin >> b[i];
}
for (int i = 0; i < n; ++i)
{
add(a[i], b[i]);
add(b[i], a[i]);
}
ll ans = 1;
for (int i = 1; i <= n; ++i)
{
if (vis[i])
continue;
vertex = 0;
edge = 0;
selfloop = 0;
dfs(i);
if (2 * vertex != edge)
{
ans = 0;
}
else if (selfloop)
{
ans = ans * n % mod;
}
else
{
ans = ans * 2 % mod;
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int T;
cin >> T;
while (T--)
{
work();
}
return 0;
}