Good Bye 2022: 2023 is NEAR

A. Koxia and Whiteboards (CF 1770 A)

题目大意

给定一个包含\(n\)个数的数组 \(a\),以及 \(m\)次操作。

\(i\)次操作将 \(a\) 中的一个数替换为\(b_i\)

\(m\)次操作后数组 \(a\)的和的最大值。

解题思路

直接贪心,每次选最小的数替换称\(b_i\)即可。

用优先队列维护最小值。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        int n, m;
        cin >> n >> m;
        priority_queue<int> qwq;
        LL sum = 0;
        for(int i = 0; i < n; ++ i){
            int a;
            cin >> a;
            sum += a;
            qwq.push(-a);
        }
        for(int i = 0; i < m; ++ i){
            int b;
            cin >> b;
            sum += qwq.top();
            sum += b;
            qwq.pop();
            qwq.push(-b);
        }
        cout << sum << '\n';
    }

    return 0;
}



B. Koxia and Permutation (CF 1770 B)

题目大意

给定\(n, k\),定义一个长度为 \(n\)的排列的价值为

\[\max_{0 \leq i \leq n-k} ( \max_{ i \leq j \leq i + k - 1} a_j + \min_{ i \leq j \leq i + k - 1} a_j ) \]

请构造一个排列,是其价值最小。

解题思路

\(k\)等于 \(1\)时,无论怎么构造代价都是 \(2n\)

\(k\)更大时,看样例容易猜到最小的代价就是 \(n+1\),最大值最小值交替放置,即 \(n, 1, n-1, 2, n-2, 3,...\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        int n, k;
        cin >> n >> k;
        for(int i = 0; i + i < n; ++ i){
            cout << n - i << ' ';
            if (n - i !=  i + 1)
                cout << i + 1 << ' ';
        }
        cout << '\n';
    }

    return 0;
}



C. Koxia and Number Theory (CF 1770 C)

题目大意

给定一个包含\(n\)个数的数组\(a\),问是否存在一个正整数 \(x\),使得任意 \(1 \leq i < j \leq n\),都有 \(gcd(a_i + x, a_j + x) = 1\)

解题思路

考虑怎样的\(x\)不会使得两个数互质。以下\(\%\)\(mod\)都是取余操作。

既然不互质,我们假设它们是公因数 \(c\) ,这意味着\(a_i \% c = a_j \% c\),且\(x = c - a_i \% c\),这样 \((a_i + x)\)\((a_j + x)\)才都是 \(c\)的倍数,因此这样的 \(x\)不能取。

那么如果对于一个公因数 \(c\),对它取余的范围 \([0,c-1]\)里全部数都不能取,那就说明不存在一个 \(x\)满足题目的条件。

\(y \in [0, c-1]\) 不能取,意味着有至少两个\(a_i \% c = y\)

因此我们枚举\(c\),统计所有 \(a_i \% c\)的取值。如果 \([0,c-1]\)中的取值出现次数都大于 \(1\),那意味着全部数都不能取,不存在此类的 \(x\)\(x\)无论取什么, \(a_i \% c\)的结果是\(c- x \% c\) 那些\(a_i\)加上 \(x\)后会有公因数 \(c\)

因为数只有\(100\),根据鸽笼原理,我们的 \(c\)最多枚举到 \(50\)即可。更大的话肯定有 \(y \in [0, c-1]\) 出现次数小于\(2\)

而对于一个\(c\),如果存在一个 \(y \in [0, c - 1]\) ,其出现次数为\(1\)\(0\),则 \(x\)可取满足 \(x \mod c = y\)的值

而对于所有的\(c\),都至少存在一个 \(y \in [0, c-1]\) ,其出现次数为\(1\)\(0\),那么就有若干个类似的同余方程 \(x \mod c = y\)

因为考虑的是公因数,最终这些 \(c\)都可以归功到质数上,那就相当于我们有若干个形如\(x \equiv y \mod c\)的同余方程组成的同余方程组,各个 \(c\)之间互质(是质数),由中国剩余定理可知该方程组一定有解,且该解可以构造出来。因此也就有解。

即如果所有的 \(c\)都至少有一个 \(y\)出现次数小于 \(2\),那么就一定存在 \(x\)满足要求。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        vector<LL> a(n);
        for(auto &i : a)
            cin >> i;

        auto check = [&](){
            if (set<LL>(a.begin(), a.end()).size() != a.size())
                return false;
            for(int i = 2; i <= n / 2; ++ i){
                vector<int> used(i);
                for(auto x : a){
                    used[x % i] ++;
                }
                if (*min_element(used.begin(), used.end()) >= 2)
                        return false;
            }
            return true;
        };
            
        if (check())
            cout << "YES" << '\n';
        else 
            cout << "NO" << '\n';
        
    }

    return 0;
}



当然,一开始的想法不是这样,我们考虑更相减损术。

\(gcd(a_i + x, a_j + x) = gcd(a_i + x, |a_j - a_i|)\)

如果其值不为\(1\),我们假设是 \(c\),那么 \(x\)就不能取值使得 \(a_i+x\)\(c\)的倍数,即 \(x \neq c - a_i \% c\)

那么我们枚举 \(c\),再枚举 \(a_i\),如果存在 \(|a_i - a_j|\)\(c\)的倍数,那么 \(x\)就有一个不能取的值。

当遍历完 \(a_i\)后, \(x\)不能取的值覆盖了 \([0,c-1]\),那么就不存在\(x\) 满足条件了。

否则就有一个满足条件的同余方程。

对所有\(c\)都判断一遍,如果都有能取的值,同上,根据中国剩余定理可知一定有解。

\(c\)的数量,根据鸽笼原理,一个 \(a_i\)至多占一个位置,因此 \(c\)枚举到 \(100\)就可以了。

下面代码复杂度比较高,因为多了个判断差值是不是\(c\)的倍数的循环,多了个\(O(n)\),事先对差值分解质因数可以降到 \(O(1)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const LL p_max = 100;
LL pr[p_max], p_sz;
void get_prime() {
    static bool vis[p_max];
    FOR (i, 2, p_max) {
        if (!vis[i]) pr[p_sz++] = i;
        FOR (j, 0, p_sz) {
            if (pr[j] * i >= p_max) break;
            vis[pr[j] * i] = 1;
            if (i % pr[j] == 0) break;
        }
    }
}

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);

    get_prime();
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        vector<LL> a(n);
        for(auto &i : a)
            cin >> i;

        auto check = [&](){
            for(int pp = 0; pp < p_sz; ++ pp){
                int p = pr[pp];
                int cnt = 0;
                vector<int> used(p);
                for(int i = 0; i < n; ++ i){
                    bool ok1 = false;
                    for(int j = i + 1; j < n; ++ j){
                        LL dis = abs(a[i] - a[j]);
                        if (dis == 0)
                            return false;
                        if (dis % p == 0){
                            ok1 = true;
                            break;
                        }
                    }
                    if (ok1){
                        if (!used[a[i] % p]){
                            ++ cnt;
                            used[a[i] % p] = true;
                        }
                    }
                }
                if (cnt == p)
                    return false;
            }
            return true;
        };
            
        if (check())
            cout << "YES" << '\n';
        else 
            cout << "NO" << '\n';
        
    }

    return 0;
}



D. Koxia and Game (CF 1770 D)

题目大意

\(A\)\(B\)玩游戏,有三个包含\(n\)个数的数组\(a,b,c\),然后依次对\(i \in [0, n - 1]\) 进行操作,得到数组\(d\)

  • \(A\)\(a_i, b_i, c_i\)中的一个值丢掉
  • \(B\)从剩下的两个数选一个数,作为\(d_i\)

如果最终数组\(d\)是一个排列,则 \(A\)获胜,否则 \(B\)获胜。

现在给定数组 \(a,b\),假设两人绝顶聪明,问有多少个数组\(c\),满足\(A\)存在必胜策略。

解题思路

手玩一下会发现\(A\)取走后剩下的两个数一定是相同的,即让 \(B\)取的数一定是确定的。感性原因如下:

首先,如果剩下的两个数有一个是\(B\)先前取过的,那么 \(B\)一定会取这个数,这样 \(d\)就不是一个排列, \(B\)赢了。

因此,剩下的两个数必须都是 \(B\)没取过的数,如果这两个数不一样,是\(a_i,b_i\),由于不确定 \(B\)会取谁,在之后的状态里,必须还包含 \(a_i,b_i\)这两个值,且\(c_i\)还是个未取过的数,此时\(A\)只要丢弃先前 \(B\)选的 \(a_i\)\(b_i\),就能保证剩下的数一定都是 \(B\)没取过的。但再之后的情况,就是我们不确定 \(a_i,b_i,c_i\)是哪一个数没被取过,我们需要对每种情况去应对,此时就几乎做不到保证每次剩下的两个数都是 \(B\)没取过的。

因此得出一个结论就是,对于 \(c_i\),如果 \(a_i==b_i\),那么 \(c_i\)任取(随后\(A\)丢掉 \(c_i\)),有 \(n\)种方式 。而如果\(a_i \neq b_i\),那么要么 \(c_i = a_i\),要么 \(c_i = b_i\)\(A\)丢掉仅出现一次的那个数,这样每次 \(B\)面对两个相同的数无从选择,只能乖乖照做。

既然知道了数组\(c\)的构造方法,那怎么判断统计满足条件的数量呢。

首先对于\(a_i=b_i\)的那些下标,其取值肯定是 \(a_i\),且方案数是 \(n\)

然后我们考虑 \(a_i \neq b_i\)的下标,究竟要选择 \(a_i\)还是 \(b_i\)

一个朴素的想法就是搜索,假设选择 \(a_i\), 然后搜后续情况,再假设选\(b_i\),搜后续情况。

搜后续情况的时候,有个容易想到的技巧,就是如果我选择了 \(a_i\),那么其他包含 \(a_i\)的下标只能选另一个数了。

下标与下标之间由于 \(a_i\)\(b_i\)的关系构成了一张图。如果能在这张图上搜索就更好了。

考虑这张图该如何构造,无非就两种,一种是是下标,是数字,另一种是是数字,是下标。

分别考虑这两张图,后者才能完美的符合我们的要求:如果我选择了 \(a_i\),那么我要找其他包含 \(a_i\)的下标。而且前一张图的构造复杂度是\(O(n^2)\)会超时(

而如何体现我们选择了\(a_i\)呢?

因为是下标,连接了 \(a_i\)\(b_i\),如果我们选择了 \(a_i\),那么我们就给该边赋予一个指向\(a_i\) 的方向,那其他与\(a_i\)的连边的方向只能背离 \(a_i\)。最后如果所有数的入度都为\(1\),那么这是个可行的方案。

那方案数怎么统计呢?

首先由于连边,我们会得到若干个连通块,各个连通块的取值的乘积就是最终方案数。

由于只有\(n\)个下标,要选出 \(n\)个不同的数,对于一个连通块而言,如果它有\(x\)个点,那意味着必须有 \(x\)条边(即 \(x\)个下标),才能达成\(x\)个下标选 \(x\)个数的条件,才有可能合法。否则:

  • 如果一个连通块有\(x\)个点, \(x-1\)条边,那意味着有一个点(数)不能被选到,则\(d\)也不是一个排列
  • 如果一个连通块有\(x\)个点, \(x+1\)条边,那意味着有一个点(数)会被两个下标分别选了一次,则\(d\)也不是一个排列

而对于 一个有 \(n\)个点, \(n\)条边的连通块,其实就是一棵基环树(一棵树加一条边)。考虑对这个基环树的边赋予方向的话,容易发现只有两种情况:该基环数里唯一的环的方向是顺时针还是逆时针,而其他非环边的方向都是背离环的。

注意如果是自环的话,此时自环边的顺逆没有任何区别,此时的方案数应是上面提到的\(n\),即 \(c_i\)任意取。

因此总结以上思考可以得出,如果各个连通块均满足点数等于边数,则存在数组\(c\)。而有自环的连通块贡献答案\(n\),无自环的贡献 \(2\),答案就是这些数的累乘。

假设有自环的连通块数量是\(cnt1\),无自环的是 \(cnt2\),那答案就是 \(n^{cnt1} \times 2^{cnt2}\)

而由于一条边贡献了两个点的度数,因此点数等于边数的条件可以转换为2倍点数等于点度数和

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

const LL mo = 998244353;

long long qpower(long long a, long long b){
    long long qwq = 1;
    while(b){
        if (b & 1)
            qwq = qwq * a % mo;
        a = a * a % mo;
        b >>= 1;
    }
    return qwq;
}

long long inv(long long x){
    return qpower(x, mo - 2);
}

const LL inv2 = inv(2);

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        vector<int> du(n, 0), sz(n, 1);
        vector<int> fa(n);
        iota(fa.begin(), fa.end(), 0);
        vector<int> a(n), b(n);
        LL ans = 1;
        for(auto &i : a){
            cin >> i;
            -- i;
        }
        for(auto &i : b){
            cin >> i;
            -- i;
        }
        function<int(int)> findfa = [&](int x){
            return x == fa[x] ? x : fa[x] = findfa(fa[x]);
        };
        auto calc = [&](){
            for(int i = 0; i < n; ++ i){
                du[a[i]] ++;
                du[b[i]] ++;
                if (a[i] == b[i]){
                    ans = ans * n % mo * inv2 % mo;
                }
            }
            for(int i = 0; i < n; ++ i){
                int fx = findfa(a[i]);
                int fy = findfa(b[i]);
                if (fx != fy){
                    fa[fx] = fy;
                    du[fy] += du[fx];
                    sz[fy] += sz[fx];
                }
            }
            for(int i = 0; i < n; ++ i){
                int ff = findfa(i);
                if (ff == i){
                    debug(ff, du[ff], sz[ff]);
                    if (du[ff] != sz[ff] * 2)
                        return false;
                    ans = ans * 2 % mo;
                }
            }
            return true;
        };
        if (calc()){
            cout << ans << '\n';
        }else 
            cout << 0 << '\n';
    }

    return 0;
}



E. Koxia and Tree (CF 1770 E)

题目大意

给定一棵包含\(n\)个节点的树,第\(i\)条边连接了节点 \(x_i, y_i\)。有\(k\)只蝴蝶在树的节点上,第\(i\)只蝴蝶在节点 \(a_i\)上。一个节点至多有一只蝴蝶。

依次进行以下操作:

  • 依次对于边\(i = 1,2,3,...,n-1\),等概率为该边赋一个方向
  • 依次对于边\(i = 1,2,3,...,n-1\),如果边的起始点有蝴蝶,且终点没有蝴蝶,则该蝴蝶会飞到终点上

最后,等概率选择两只蝴蝶,计算它们的距离,即边数。

问该边数的期望值。

解题思路

虽然初看这题感觉是神仙期望题,但按照套路来还是不难。

由期望的线性可加性,且所有情况都是等概率的,既然是边数,我们就考虑每条边对期望的贡献。

如果仅仅是选择两个蝴蝶问它们的期望距离,对于一条边来说,它对答案的贡献次数就是\(sz_v * (k - sz_v)\) ,其中\(sz_v\)就是该边深度更深的节点的子树的蝴蝶数。

如果多了移动操作,那么 \(sz_v\)的值就不是固定了,而是有概率的。

而边对答案的贡献次数就变成了期望次数(?存疑,还在思考

神奇的代码



F. Koxia and Sequence (CF 1770 F)

题目大意

<++>

解题思路

<++>

神奇的代码



G. Koxia and Bracket (CF 1770 G)

题目大意

<++>

解题思路

<++>

神奇的代码



H. Koxia, Mahiru and Winter Festival (CF 1770 H)

题目大意

<++>

解题思路

<++>

神奇的代码



posted @ 2022-12-31 17:54  ~Lanly~  阅读(165)  评论(0编辑  收藏  举报