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;
}
posted @ 2022-12-31 16:14  何太狼  阅读(59)  评论(0编辑  收藏  举报