Codeforces Round 716 Div.2

已经连着两场掉分了,麻了麻了……

这场开的比较早,在寝室里打的,九点多的时候还比较闹,有点难静下心来思考。加之前一晚喝了酒,感觉脑子里一片混沌——这大概是发挥不好的一点场外原因,还是希望下一场能上大分。

近期还有一个问题是出题出的比较慢,以往我前两题出的都是非常快的,但最近越来越慢,可能要到四五十分钟才能出完,浩元学长这时候C都已经出思路了……

A - Perfectly Imperfect Array

题目大意就是给定一个数组,问能不能从中取出一个子序列,使得其中所有数的乘积为一个完全平方数。如果可以输出NO,不可以输出YES。

对于任意个完全平方数的乘积$a^2b^2\cdotsc^2 = (ab*\cdots *c)^2$,也一定是完全平方数。所以只要有数组中有非完全平方数,那么取该数为子序列就为YES,否则数组中元素全为完全平方数,任意个组合结果仍是完全平方数,为NO。

#include <bits/stdc++.h>

using namespace std;

int T, n;
vector<int> v;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    cin >> T;
    while (T--) {
        cin >> n;
        v.resize(n);
        for (int &it:v) cin >> it;
        bool flag = true;
        for (int it:v) {
            if (it == 1) continue;
            int tmp = sqrt(it);
            if (tmp * tmp != it) {
                flag = false;
                break;
            }
        }
        if (!flag) cout << "YES\n";
        else cout << "NO\n";
    }
    return 0;
}

B - AND 0, Sum Big

一开始读掉了这一条件:

  • the sum of its elements is as large as possible.
  • 所有元素的和尽可能大。

事实上这条就是解题的关键。首先要满足所有数按位与结果为$0$,其实就是要所有数的同位数字中至少出现一个$0$。我们又知道,即使是二进制下我们也可以仿照十进制进行竖式运算,所以要同时让按位与结果为$0$,还要所有元素的和尽可能大,一定是在所有数的同位数字中有且仅出现一个$0$,这样得到的元素和为$(n-1)*(2^k-1)$。

那很容易就能得知本题的答案,即每位共$n$个数字,从中选一个作为$0$,其它的作为$1$,总共选$k$位,根据乘法原理答案就是$n^k$。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll MOD = 1e9 + 7;

int T;
ll n, k;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    cin >> T;
    while (T--) {
        cin >> n >> k;
        ll ans = 1;
        for (int i = 1; i <= k; i++)
            ans = (ans * n) % MOD;
        cout << ans << endl;
    }
    return 0;
}

C - Product 1 Modulo N

写这题时,我先用下面代码打了个表:

#include <bits/stdc++.h>

using namespace std;

int n;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    for (n = 1; n <= 20; n++) {
        vector<int> ans;
        for (int i = 0; i < (1 << (n - 1)); i++) {
            int tmp = i;
            int cnt = 1, mul = 1;
            vector<int> v;
            while (tmp) {
                if (tmp & 1) {
                    mul *= cnt;
                    v.push_back(cnt);
                }
                cnt++;
                tmp >>= 1;
            }
            if (mul % n == 1 && v.size() > ans.size()) ans = v;
        }
        cout << "n = " << n << ": \n";
        cout << ans.size() << endl;
        for (int it:ans) cout << it << ' ';
        cout << endl;
    }
    return 0;
}

观察了一下$1\leq n\leq 20$下的结果:

n = 1: 
0

n = 2: 
1
1 
n = 3: 
1
1 
n = 4: 
1
1 
n = 5: 
3
1 2 3 
n = 6: 
1
1 
n = 7: 
5
1 2 3 4 5 
n = 8: 
4
1 3 5 7 
n = 9: 
5
1 2 4 5 7 
n = 10: 
3
1 3 7 
n = 11: 
9
1 2 3 4 5 6 7 8 9 
n = 12: 
4
1 5 7 11 
n = 13: 
11
1 2 3 4 5 6 7 8 9 10 11 
n = 14: 
5
1 3 5 9 11 
n = 15: 
8
1 2 4 7 8 11 13 14 
n = 16: 
8
1 3 5 7 9 11 13 15 
n = 17: 
15
1 2 3 4 5 6 7 8 10 11 12 13 14 15 16 
n = 18: 
5
1 5 7 11 13 
n = 19: 
16
1 2 3 4 5 6 7 8 9 11 12 13 14 15 16 17 
n = 20: 
8
1 3 7 9 11 13 17 19 

然后开始找规律,发现首先$n$为偶数时,答案中只有奇数。其次,$n$为奇数时,答案都是从$1\sim n-1$删去几个数构成的。

当天有点迷糊,觉得肯定有进一步的规律,但没想出来是什么,就去求助了下学长。发现学长很早之前就已经给了个思路(那时我甚至不知道他说的是C题)——去掉所有与$n$不互素的数,然后特判$n=4k+2$的情况,这种时候要额外去掉$n-1$。我按这个标准核验了下表,发现似乎确实如此,于是码了下面的代码:

#include <bits/stdc++.h>

using namespace std;

int n;
vector<int> prime, ans;

bool check(int k) {
    int len = sqrt(k);
    for (int i = 2; i <= len; i++)
        if (k % i == 0) return false;
    return true;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    cin >> n;
    if (n < 5) return cout << "1\n1\n", 0;
    if (check(n)) {
        cout << n - 2 << endl;
        for (int i = 1; i < n - 2; i++)
            cout << i << ' ';
        cout << n - 2 << endl;
        return 0;
    }
    int tmp = n;
    while (tmp > 1) {
        for (int i = 2; i <= n; i++)
            if (tmp % i == 0) {
                tmp /= i;
                prime.emplace_back(i);
                break;
            }
    }
    for (int i = 1; i < n; i++) {
        bool flag = true;
        for (int it:prime) {
            if (i % it == 0) {
                flag = false;
                break;
            }
        }
        if (flag) ans.emplace_back(i);
    }
    if ((n - 2) % 4 == 0 && ans.back() == n - 1) ans.pop_back();
    cout << ans.size() << endl;
    for (int i = 0; i < ans.size() - 1; i++) cout << ans[i] << ' ';
    cout << ans.back() << endl;
    return 0;
}

最后几秒钟没提交上去,当时还小伤心了会儿。结果最后等System Test结束后补交一发WA了,说明上面的贪心处理策略还是有问题的。

不过研究互素的方向是正确的。

首先我们假定选定的序列包含$a$,且其它数乘积为$x$(模$n$),那么有$x$是$a$的逆元,$a$就满足$\gcd(a,n)=1$,否则不能入选。

我们把所有该选的$a$边累乘边取模,可以得到一个小于$n$的数$mul$,$mul$肯定与$n$互质,所以$mul$一定是在前面取过的,如果$mul\neq 1$,就不取这个数。

我不太擅长数论题,只能一知半解借助Tutorial和网络资源理解理解,代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int n;
vector<int> v;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    cin >> n;
    ll mul = 1;
    for (int i = 1; i < n; i++)
        if (__gcd(i, n) == 1) {
            mul = mul * i % n;
            v.emplace_back(i);
        }
    mul %= n;
    if (mul != 1)
        for (auto it = v.begin(); it != v.end(); it++)
            if (*it == mul) {
                v.erase(it);
                break;
            }
    cout << v.size() << endl;
    for (int i = 0; i < v.size() - 1; i++) cout << v[i] << ' ';
    cout << v.back() << endl;
    return 0;
}
Next
Previous