One contestant of team004. Rank 1. Solved 7/12.

A. Tree Destruction

图论+组合数,待补充

B. Encore

计算几何,待补充

C. Konnakol

题意

无穷次重复斐波那契数列的前八项,构成一个序列。

给定整数 \(n, k\),提取出前 \(n\) 个数,输出第 \(k\) 个数。

思路

首先,\(n\) 是没用的。

其次,取模即可。

时间复杂度:\(O(1)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    vector<int> p = {1, 1, 2, 3, 5, 8, 13, 21};
    int sb, x;
    cin >> sb >> x;
    cout << p[(x - 1) % 8] << "/8\n";
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t --) solve();
}

骗人 \(\times 1\)

D. Data Mining and Big Data

题意

给定一个只包含 \(0\) \(1\) 的字符串,长度为 \(2^k, k \in [0, 2]\),定义一次操作为:将当前的字符串中的所有二进制数取反,并拼接到原字符串的后面。

按照如上操作进行若干次后,可得一个 \(1024GB\) 的数据,输出数据中 \(0\) 的个数。

思路

显然,最后 \(0\)\(1\) 的个数是相等的,那么我们直接输出长度的一半即可。

不用敲样例,答案就是 \(1 << 32\)

时间复杂度:\(O(1)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    string s;
    cin >> s;
    cout << (1ll << 32) << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t --) solve();
}

不会真有人敲错吧(

E. Function

题意

初始状态下,令方程 \(f(x) = 0\)

给定 \(q\) 个询问,每个询问会给定操作序号,以及操作所需的参数:

  1. 给定两个整数 \(a, b\),将 \(f(x)\) 更新为 \(f(x) + |x - a| + b\)

  2. 输出让 \(f(x)\) 的值最小的 \(x\),以及 \(f(x)\) 的最小值。

对于询问,执行对应的操作。

思路

我们把 \(|x - a|\)\(b\) 分成两部分考虑。

首先很明显,无论 \(x\) 是什么,右边的值都不会受到影响,因此我们可以在更新的时候顺便记录右边的值的总和。

其次,在更新了 \(n\) 次后,左边的值的总和为 \(|x - a_1| + |x - a_2| + \ldots + |x - a_n|\)

不难发现,要让这个值最小,\(x\) 就是 \(a_1, a_2, \ldots, a_n\) 的中位数。

因此,本题最后归结到,如何动态维护中位数。

对于这个,考虑到码量,我们会优先选择对顶堆,而非线段树。

何为对顶堆?小根堆维护前 \(\frac{n}{2}\) 大的数,剩余的数由大根堆维护。

那么,我们只要保持大小根堆的大小的差值不大于 \(1\),即可保证大根堆的队头就是中位数。

在维护大小根堆的同时,我们根据元素的位置维护左半部分的和即可。

时间复杂度:\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 1e7 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int q;
    cin >> q;
    priority_queue<int> g;
    priority_queue<int, vector<int>, greater<>> l;
    int sum1 = 0, sum2 = 0;
    while(q --){
        int op;
        cin >> op;
        if(op == 1){
            int a, b;
            cin >> a >> b;
            sum2 += b;
            if(!l.empty() && a > l.top()){
                l.emplace(a), sum1 += a;
            }else{
                g.emplace(a), sum1 -= a;
            }
            if(g.size() > l.size() + 1){
                l.emplace(g.top()), sum1 += g.top() * 2, g.pop();
            }
            if(l.size() > g.size()){
                g.emplace(l.top()), sum1 -= l.top() * 2, l.pop();
            }
        }else{
            cout << g.top() << ' ';
            int ans = sum1 + sum2;
            if(g.size() > l.size()) ans += g.top();
            cout << ans << '\n';
        }
    }
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t --) solve();
}

妙捏

F. A * B Problem

题意

给定三个数 \(a, b, c\),定义一次操作为:选择两个数,将某个数 \(+1\),另一个数 \(-1\)

输出最小的操作数,使得存在某两个数的乘积为第三个数。

思路

我们设最后得到的 \(a, b, c\) 满足 \(ab = c\)

那么 \(a + b + ab = sum\)

左右各加 \(1\),化简得到 \((a + 1)(b + 1) = sum + 1\)

也就是说,\(a + 1\)\(sum + 1\) 的因子。

因此,如果这道题的数据量不大,那么解法就是线性筛+分解质因数+\(\mathtt{dfs}\)枚举因子。

wait,你可能会疑惑,为什么可以 \(\mathtt{dfs}\)

事实上,\(1e18\) 的因子数量的数量级只有 \(1e5\) 左右,因此暴力是可行的。

好,看一下数据范围 \(1e18\),寄。

这边需要用到 Pollard Rho 算法,用 \(O(n ^ {\frac{1}{4}})\) 的复杂度完成分解质因数。

因此,在得到因子后,我们将 \(a, b, c\) 进行排列,并与之前的 \(a, b, c\) 分别进行作差取绝对值后 求和除 \(2\),最后取最小值即可。

时间复杂度:\(O(n ^ {\frac{1}{4}}p)\) ,p为因子个数

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

/* *************************************************
 * Miller_Rabin 算法进行素数测试
 * 速度快可以判断一个 < 2^63 的数是不是素数
 *
 **************************************************/
const int S = 8; //随机算法判定次数一般 8∼10 就够了
// 计算 ret = (a*b)%c a,b,c < 2^63
int mult_mod(int a, int b, int c) {
    a %= c;
    b %= c;
    int ret = 0;
    int tmp = a;
    while (b) {
        if (b & 1) {
            ret += tmp;
            if (ret > c)ret -= c;//直接取模慢很多
        }
        tmp <<= 1;
        if (tmp > c)tmp -= c;
        b >>= 1;
    }
    return ret;
}

// 计算 ret = (a^n)%mod
int pow_mod(int a, int n, int mod) {
    int ret = 1;
    int temp = a % mod;
    while (n) {
        if (n & 1)ret = mult_mod(ret, temp, mod);
        temp = mult_mod(temp, temp, mod);
        n >>= 1;
    }
    return ret;
}

// 通过 a^(n−1)=1(mod n)来判断 n 是不是素数
// n − 1 = x ∗ 2
// 中间使用二次判断
// 是合数返回 true, 不一定是合数返回 false
bool check(int a, int n, int x, int t) {
    int ret = pow_mod(a, x, n);
    int last = ret;
    for (int i = 1; i <= t; i++) {
        ret = mult_mod(ret, ret, n);
        if (ret == 1 && last != 1 && last != n - 1)return true;//合数
        last = ret;
    }
    if (ret != 1)return true;
    else return false;
}

//**************************************************
// Miller_Rabin 算法
// 是素数返回 true,(可能是伪素数)
// 不是素数返回 false
//**************************************************
bool Miller_Rabin(int n) {
    if (n < 2)return false;
    if (n == 2)return true;
    if ((n & 1) == 0)return false;//偶数
    int x = n - 1;
    int t = 0;
    while ((x & 1) == 0) {
        x >>= 1;
        t++;
    }

    srand(time(NULL));

    /* *************** */

    for (int i = 0; i < S; i++) {
        int a = rand() % (n - 1) + 1;
        if (check(a, n, x, t))
            return false;
    }
    return true;
}

//**********************************************
// pollard_rho 算法进行质因素分解
//*********************************************
int factor[1000005];//质因素分解结果(刚返回时时无序的)
int tol;//质因素的个数,编号 0∼tol-1
int gcd(int a, int b) {
    int t;
    while (b) {
        t = a;
        a = b;
        b = t % b;
    }
    if (a >= 0)return a;
    else return -a;
}

//找出一个因子
int pollard_rho(int x, int c) {
    int i = 1, k = 2;
    srand(time(NULL));
    int x0 = rand() % (x - 1) + 1;
    int y = x0;
    while (1) {
        i++;
        x0 = (mult_mod(x0, x0, x) + c) % x;
        int d = gcd(y - x0, x);
        if (d != 1 && d != x)return d;
        if (y == x0)return x;
        if (i == k) {
            y = x0;
            k += k;
        }
    }
}

//对 n 进行素因子分解,存入 factor. k 设置为 107 左右即可
void findfac(int n, int k) {
    if (n == 1)return;
    if (Miller_Rabin(n)) {
        factor[tol++] = n;
        return;
    }
    int p = n;
    int c = k;
    while (p >= n)p = pollard_rho(p, c--);//值变化,防止死循环 k
    findfac(p, k);
    findfac(n / p, k);
}

//-------- ACM Template of kuangbin p. 30 ----------

const int inf = 0x3f3f3f3f3f3f3f3f;

vector<int> keys;
map<int, int> fact_map;
set<int> fact_set;

int qp(int a, int b) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res *= a;
        a *= a;
        b >>= 1;
    }
    return res;
}

void factorize(int x){
    fact_map.clear();
    keys.clear();
    tol = 0;
    findfac(x, 107);
    for(int i=0;i<tol;i++) {
        if(fact_map[factor[i]] == 0) keys.pb(factor[i]);
        fact_map[factor[i]] ++;
    }
}

void dfs(int x, int step){
    if(step == fact_map.size()){
        fact_set.emplace(x);
        return;
    }
    for(int i=0;i<=fact_map[keys[step]];i++){
        dfs(x * qp(keys[step], i), step + 1);
    }
}

void solve() {
    int a, b, c;
    cin >> a >> b >> c;
    int sum = a + b + c;
    factorize(sum + 1);
    fact_set.clear();
    dfs(1, 0);
    int ans = inf;
    for(auto e : fact_set) {
        if(e == 1) continue;
        int aa = e - 1, bb = (sum + 1) / e - 1, cc = sum - aa - bb;
        ans = min(ans, (abs(aa - a) + abs(bb - b) + abs(cc - c)) / 2);
        ans = min(ans, (abs(aa - a) + abs(bb - c) + abs(cc - b)) / 2);
        ans = min(ans, (abs(aa - b) + abs(bb - a) + abs(cc - c)) / 2);
        ans = min(ans, (abs(aa - b) + abs(bb - c) + abs(cc - a)) / 2);
        ans = min(ans, (abs(aa - c) + abs(bb - a) + abs(cc - b)) / 2);
        ans = min(ans, (abs(aa - c) + abs(bb - b) + abs(cc - a)) / 2);
    }
    cout << ans << '\n';
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

但凡数据量小就很签((

G. CET-4

赛时的样例出错,\(\mathtt{OJ}\) 上的重现赛的题面已修正

题意

\(n\)\(r\) 问题中答对 \(x\) 道题的概率。

思路

首先,因为如果某道题选错,可能会影响后续的正确选项的选择,所以简单的组合数是不对的。

我们不妨考虑广义容斥的做法,或者说,这就是一道广义容斥的模板题。

考虑到我比较若,这边把官方题解搬过来了,并略微做了点修改。

我们设 \(β(x)\) 为有 \(x\) 个条件的满足的方案数。

则恰好有 \(x\) 个方案满足的方案数为 \(\displaystyle{f(x)=\sum_{i=x}^{n}(-1)^{i-x}β(x) {i\choose x}}\)

对应到本题,求解步骤即为:

  1. \(β(x)\) 的意思是至少对 \(x\) 题,也就是先从 \(r\) 个空中选出 \(i\) 个是对的,然后对于剩下的 \(n-i\) 个选项,我们再选出 \(r-i\) 个填入剩下的空。 那么方案数为 \(\displaystyle{ {r \choose i} { {n-i} \choose {r-i} } (r-i)!}\)

  2. 代入公式,\(\displaystyle{f(x)=\sum_{i=x}^{r}{i \choose x}(-1)^{i-x}{r \choose i}{n-i\choose r-i }(r-i)!}\)

  3. 全部的方案数量为 \(\displaystyle{ {n \choose r} r! }\),因此将 \(f(x)\) 和这个作除即可得到答案。

当然也可以用 dp 实现。

时间复杂度:看你怎么预处理

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e6 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

int fact[N], fact_inv[N];

int qp(int a, int b) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

int inv(int x){
    return qp(x, mod - 2);
}

void init(){
    fact[0] = fact_inv[0] = 1;
    for(int i=1;i<=2e6;i++){
        fact[i] = fact[i - 1] * i % mod;
        fact_inv[i] = inv(fact[i]); //当然这边还可以优化
    }
}

int C(int n, int m){
    return fact[n] * fact_inv[n - m] % mod * fact_inv[m] % mod;
}

void solve() {
    int n, r, x;
    cin >> n >> r >> x;
    int p = 0;
    for(int i=x;i<=r;i++){
        int now = C(i, x) * C(r, i) % mod * C(n - i, r - i) % mod * fact[r - i] % mod;
        if((i - x) % 2 == 0) p = (p + now) % mod;
        else p = (p + mod - now) % mod;
    }
    cout << p * inv(C(n, r) * fact[r] % mod) % mod << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init();
    int t = 1;
    cin >> t;
    while(t --) solve();
}

论我想了半天4选1对1题的概率为什么是1/6这件事

H. Group Theory

题意

定义一次操作为将整个序列向右移动一格,并将最后一个元素放到序列的开头。

给定一个字符序列,输出进行任意次操作后是否能将序列变为回文。

思路

数据量很小,所以是一道签到题。

与其模拟放置的过程,我们不妨直接在左边和右边分别复制一遍原序列,并遍历这个新序列,若出现了长度为 \(n\) 的回文序列,那么就是可行。

时间复杂度:\(O(\frac{3}{2}n^2)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    if(n == 0){
        cout << "YES\n";
        return;
    }
    vector<string> a(3 * n  + 3);
    for(int i=0;i<n;i++){
        cin >> a[i];
        a[n + i] = a[n * 2 + i] = a[i];
    }
    bool f = false;
    for(int i=0;i<2*n;i++){
        bool cur = true;
        for(int j=0;j<n/2;j++){
            if(a[i + j] != a[i + (n - j - 1)]){
                cur = false;
                break;
            }
        }
        if(cur){
            f = true;
            break;
        }
    }
    cout << (f ? "YES\n" : "NO\n");
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

签到签到

I. Balls

题意

给定 \(n\) 个盒子,每个盒子里最多有 \(3\) 个球。

定义操作为等概率选一个盒子(包括空的),并取出一个球。

给定每个盒子内的球数,输出将所有球取出的数学期望。

思路

首先,我们来考虑 \(4\) 维 dp,其中 \(dp[i][j][k][p]\) 代表还剩 \(0, 1, 2, 3\) 个球分别对应的盒子个数 \(i, j, k, p\) 该状态的数学期望。

该状态可由下面的子状态转移得到:

  1. 从一个包含 \(3\) 个球的盒子中取出一个球,对应期望为 \(\frac{p}{n}dp[i][j][k + 1][p - 1]\)

  2. 从一个包含 \(2\) 个球的盒子中取出一个球,对应期望为 \(\frac{k}{n}dp[i][j + 1][k - 1][p]\)

  3. 从一个包含 \(1\) 个球的盒子中取出一个球,对应期望为 \(\frac{j}{n}dp[i + 1][j - 1][k][p]\)

  4. 从一个包含 \(0\) 个球的盒子中取出一个球,对应期望为 \(\frac{i}{n}dp[i][j][k][p]\)

加上自己可作为单独的一个状态的期望 \(1\),最后整理得到式子:

\(dp[i][j][k][p] = \frac{n}{j + k + p} + \frac{j}{j + k + p}dp[i + 1][j - 1][k][p] + \frac{k}{j + k + p}dp[i][j + 1][k - 1][p] + \frac{k}{j + k + p}dp[i][j][k + 1][p - 1]\)

有趣的是,\(i + j + k + p = n\),因此我们可以拿掉一维。

最后得到状态转移方程:

\(dp[i][j][k] = \frac{n}{i + j + k} + \frac{i}{i + j + k}dp[i - 1][j][k] + \frac{j}{i + j + k}dp[i + 1][j - 1][k] + \frac{k}{i + j + k}dp[i][j + 1][k - 1]\)

时间复杂度:\(O(n ^ 3)\)

对应AC代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

double f[305][305][305];
int a[5], n;

signed main() {
    cin >> n;
    for(int i = 1, x; i <= n; i++) cin >> x, a[x]++;
    for(int k = 0; k <= n; k++) {
        for(int j = 0; j <= n; j++) {
            for(int i = 0; i <= n; i++) {
                if(i || j || k) {
                    if(i) f[i][j][k] += f[i - 1][j][k] * i / (i + j + k);
                    if(j) f[i][j][k] += f[i + 1][j - 1][k] * j / (i + j + k);
                    if(k) f[i][j][k] += f[i][j + 1][k - 1] * k / (i + j + k);
                    f[i][j][k] += 1.0 * n / (i + j + k);
                }
            }
        }
    }
    cout << setprecision(10);
    cout << fixed << (f[a[1]][a[2]][a[3]]);
    return 0;
}

自己写的dp状态重复了(也就是以一种递归的方式正推),十分头大于是找了一篇洛谷的题解研究了一波((

J. Wish You Can Have Fun Today

题意

输出 "Wish We Can Have Fun Today."

思路

如题,别打错。

时间复杂度:\(O(1)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 1e9 + 7, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    cout << "Wish We Can Have Fun Today.\n";
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    //cin >> t;
    while(t --) solve();
}

有人在比赛的刚开始把这题交错位置了,我不说是谁(

K. Chessboard City

题意

给定一个由 \(n\) 个横向街道和 \(m\) 个纵向街道组成的矩形城市,定义一条横向或纵向的街道只能有一个标记,只能标记在十字路口上。

给定标记总数 \(k\),输出方案数。

思路

排列组合的签到题。

先从 \(n\) 个横向街道里选择 \(k\) 个街道,不考虑顺序,再从 \(m\) 个纵向街道里选择 \(k\) 个街道,考虑顺序,最后取模即可。

也就是说,答案是 \(C^k_nA^k_m\)

注意需要线性求逆元哦,估计就卡死在这里了吧。

时间复杂度:看你怎么预处理

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 1e7 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

int fact[N], inv[N], fact_inv[N];

int qp(int a, int b) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

void init(){
    fact[0] = inv[0] = fact_inv[0] = fact[1] = inv[1] = fact_inv[1] = 1;
    for(int i=2;i<=1e7;i++) {
        fact[i] = fact[i - 1] * i % mod;
        inv[i] = inv[mod % i] % mod * (mod - mod / i) % mod;
        fact_inv[i] = fact_inv[i - 1] * inv[i] % mod;
    }
}

int C(int n, int m){
    return fact[n] * fact_inv[n - m] % mod * fact_inv[m] % mod;
}

int A(int n, int m){
    return fact[n] * fact_inv[n - m] % mod;
}

void solve() {
    int n, m, k;
    cin >> n >> m >> k;
    cout << C(n, k) * A(m, k) % mod << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    init();
    int t = 1;
    cin >> t;
    while(t --) solve();
}

签到签到

L. Game

题意

给定 \(n\) 个商店,每个商店将会在 \(0.5, 1.5, 2.5, \ldots\) 时刻售出一份库存游戏。

给定家到每个商店需要的时间,以及每个商店的库存,输出买到游戏的最短时刻,以及对应的商店的编号(有多个满足条件输出全部)。

思路

首先,如果到达时间大于库存量,那么到的时候就卖光了。

其次,我们直接暴力枚举找出最小值以及对应的商店即可。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 1e7 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    int mn = inf;
    set<int> ans;
    for(int i=0;i<n;i++){
        int a, b;
        cin >> a >> b;
        if(a > b) continue;
        if(a < mn){
            mn = a, ans.clear(), ans.emplace(i + 1);
        }else if(a == mn) ans.emplace(i + 1);
    }
    if(mn == inf){
        cout << -1 << '\n';
        return;
    }
    cout << mn << '\n';
    for(auto e: ans) cout << e << ' ';
    cout << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

居然没看到反面有题哈哈哈哈(x