Educational Codeforces Round 86 A - E 解题报告

A. Road To Zero

题意:

  有\(x, y\)两个数,你的目的是让\(x = 0\ and\ y = 0\),你可以花费\(a\)元使\(x\)\(y\)加减\(1\),也可以花费\(b\)元使\(x\)\(y\)加减\(1\)。求最小代价?

题解:

   \(if\ x*y\leq 0,ans = (abs(x) + abs(y)) * a\)
   \(else,ans = min(abs(x + y) * a, min(abs(x), abs(y)) * b + abs(x - y) * a)\)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main(){
    int t; cin >> t;
    while(t --){
        ll a, b, x, y;
        cin >> x >> y >> a >> b;
        ll ans;
        if((x <= 0 && y >= 0) || (x >= 0 && y <= 0)){ // 异号只有一种做法
            ans = (abs(x) + abs(y)) * a;
        }
        else{ // 同号就两种情况取小值
            ll ans1, ans2;
            ans1 = (abs(x) + abs(y)) * a;
            ans2 = min(abs(x), abs(y)) * b + abs(abs(y) - abs(x)) * a;
            ans = min(ans1, ans2);
        }
        cout << ans << endl;
    }
    return 0;
}

B. Binary Period

题意

  给你一个字符串\(t\),构造字符串\(s\),使得\(t\)\(s\)个子序列(不连续),并且\(s\)的循环周期\(k\)最小。
  例:\(s = 0010010010\),则\(k = 3\)。要求\(len(s) \leq 2 * len(t)\)

题解

  根据他的长度要求,显然\(k \leq 2\),所以我们只需要判断\(t\)是否是全\(1\)或全\(0\),如果是,直接输出\(t\),否则我们构造一个长度为\(2 * len(t)\)的字符串\(s\),就可以符合要求

代码

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

int main(){
    int t; cin >> t;
    while(t --){
        string t, s = "";
        cin >> t;
        int mark = 0;
        for(int i = 1; i < t.size(); i ++) // 判断t是否包含0和1
            if(t[i] != t[i - 1]){
                mark = 1;
                break;
            }
        if(!mark){
            cout << t << endl;
        }else{
            for(int i = 0; i < t.size(); i ++){
                s += '0'; s += '1';
            }
            cout << s << endl;
        }
    }
    return 0;
}

C. Yet Another Counting Problem

题意:

  在区间\([l, r]\)中有多少个x满足\(((x\ mod\ a)\ mod\ b)≠((x\ mod\ b)\ mod\ a)\)

题解:

  数论上来先打表!打表出奇迹!

  多枚举几个\(a, b, l, r\),把\(x\)打出来看,就可以发现其中的规律

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 210;

int f[N * N];


// 打表
// void makeChart(int a, int b, int l, int r){
//     for(int i = l; i <= r; i ++){
//         if((i % a) % b != (i % b) % a){
//             cout << i << endl;
//         }
//     }
// }

ll cal(ll n, ll x){
    return n / x * f[x] + f[n % x];
}

int main(){
    // freopen("output.txt", "w", stdout);
    // makeChart(4, 6, 1, 100);
    int t; cin >> t;
    while(t --){
        ll a, b, l, r, q;
        cin >> a >> b >> q;
        memset(f, 0, sizeof f);
        for(int i = 1; i <= a * b; i ++){
            f[i] = f[i - 1] + (i % a % b != i % b % a);
        }
        while(q --){
            cin >> l >> r;
            cout << cal(r, a * b) - cal(l - 1, a * b) << " ";
        }
        puts("");
    }
    return 0;
}

D. Multiple Testcases

题意:

  给你\(n\)个数\(m[i] \leq k(i \leq n)\),再给你\(k\)个数\(c[i](i \leq k)\),将这\(n\)个数分组,要求每组\(\geq i\)的数的个数\(\leq c[i]\),问最少可以分几组?

题解:

  先求出最小的分组数,预处理出\(\geq i\)的数的个数\(cnt[i]\),那么分组数\(ans = max(ans, \left \lceil \frac{cnt[i]}{c[i]} \right \rceil)\),然后将\(m\)排序,依次插入这\(ans\)个组,则第\(i\)组里面的数为:\({m[i], m[i + ans], m[i + 2 * ans] ...}\)
  证明:以任意一组举例,其中\(\geq i\)的数的个数\(t = \left \lceil \frac{cnt[i]}{ans} \right \rceil\),又因为\(ans \geq \left \lceil \frac{cnt[i]}{c[i]} \right \rceil\),那么显然\(t \leq c[i]\),故符合题意。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;

int n, k;
int m[N], c[N], cnt[N];
vector<int> vec[N];

int main(){
    cin >> n >> k;
    for(int i = 1; i <= n; i ++){
        scanf("%d", &m[i]);
        cnt[m[i]] ++; // 记每个数出现的次数
    }
    for(int i = 1; i <= k; i ++){
        scanf("%d", &c[i]);
    }
    for(int i = k; i >= 1; i --){
        cnt[i] += cnt[i + 1]; // 算出>=i的个数
    }
    int ans = 1;
    for(int i = 1; i <= k; i ++){ // 计算组数
        int tmp = cnt[i] / c[i] + (cnt[i] % c[i] != 0);
        ans = max(ans, tmp);
    }

    sort(m + 1, m + n + 1);
    for(int i = 1; i <= n; i ++){ // 轮流插入
        vec[i % ans].push_back(m[i]);
    }

    cout << ans << endl;
    for(int i = 0; i < ans; i ++){
        cout << vec[i].size();
        for(int j = 0; j < vec[i].size(); j ++){
            printf(" %d", vec[i][j]);
        }
        puts("");
    }
    return 0;
}

E. Placing Rooks

题意:

  在\(n \times n\)的棋盘中,你可以放\(n\)枚棋子,棋子可以攻击同行和同列距离最近的棋子(参考象棋中的车),在你摆放完所有棋子后,要求有\(k\)对棋子可以互相攻击,并且对于没有摆放棋子的空格来说,他的同行或同列至少有一枚棋子。求摆放的方案数?

题解:

  显然要么每行都有一个棋子,要么每列都有一个棋子。
  我们考虑每行都有一个棋子。那么攻击的棋子只能在列中产生了。假设一列中有\(x\)枚棋子,那么有\(x - 1\)对棋子可以相互攻击,也就是说\(n\)枚棋子最多有\(n - 1\)对棋子可以相互攻击,那么对于\(k > n - 1\)的,我们可以直接过滤掉。
  那么对于每行都有一个棋子的情况下,要符合条件我们应该摆多少列呢(例如我们摆\(n\)列显然不符合条件)?
  假设我们摆\(t\)列,那么每列摆了\(c_1,c_2,c_3......c_t\)个,则可以得到两个方程:

\[c_1+c_2+c_3+......+c_t = n \]

\[(c_1-1)+(c_2-1)+(c_3-1)+......+(c_t-1)=k \]

  相减即可得到:\(t = n - k\),即要符合条件我们必须摆\(n - k\)列。
  接下来就是组合数学的问题了,我们将\(n\)枚棋子放入\(t\)列,每一枚棋子都有\(t\)种方法,则共有\(t^n\)种情况。但是我们发现可能出现了不足\(t\)列的情况,所以我们需要减去这些情况,即那些只放了\(1,2,3,...,t-1\)列的情况,但是这样不好计算,所以我们需要换种计算思路。
  我们在\(t\)列中选出一个空列(没有摆放棋子的),然后减去\(n\)枚棋子在剩余\(t - 1\)列摆放的情况,但和\(t\)列一样,也会出现没有摆满的情况,所以我们就减多了(由于\(t-1\)列中也有空列,就会再次被选出来,所以重复计算了),那么我们就需要加上多减的,再在\(t-1\)列中选出一个空列,加上\(n\)枚棋子在剩余\(t-2\)列中摆放的情况。但显然是存在摆不满的情况的,所以我们又加多了,又需要减,如此重复下去,直到最后只剩\(1\)列的情况。那么我们可以得到公式:

\[ans = t^n-C_{t}^{1} \times (t-1)^n + C_{t}^2 \times (t-2)^n - C_{t}^3 \times (t-3) ^ n + ... \]

  化简一下即:

\[ans = \sum_{i=0}^{t}(-1)^i \times C_t^i \times (t-i)^n \]

  由于还要从\(n\)列里面选出\(t\)列,所以还要乘以\(C_n^t\)。最后我们将行列颠倒考虑,即还有相同的方案数,所以\(ans = ans \times 2\),但是当\(k = 0\)的时候,即没有两枚棋子可以相互攻击,即每行每列只有一枚棋子,这种情况下行列的情况都是一样,所有不用\(\times 2\)

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 200010, mod = 998244353;
typedef long long ll;

int n, k;
ll fact[N]; // 阶乘

ll fastPow(ll a, ll b){ // 快速幂
    ll res = 1;
    while(b){
        if(b & 1) res = res * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return res;
}

ll cal(int a, int b){ // 计算组合数
     return fastPow(fact[b] * fact[a - b] % mod, mod - 2) * fact[a] % mod;
}

int main(){
    // freopen("input.txt", "r", stdin);
    cin >> n >> k;
    if(k >= n) { puts("0"); return 0; }
    fact[0] = 1;
    for(int i = 1; i <= n; i ++){
        fact[i] = fact[i - 1] * i % mod;
    }
    int c = n - k;
    ll ans = 0;
    for(int i = 0; i <= c; i ++){ // 求和
        ans = ((ans + cal(c, i) * fastPow(-1, i) * fastPow(c - i, n)) % mod + mod) % mod;
    }

    ans = ans * cal(n, c) % mod; // 乘上从n列里选(n - k)列的方案数

    if(k){
        ans = (ans << 1) % mod;
    }

    cout << ans << endl;

    return 0;
}
posted @ 2020-05-08 16:11  nonameless  阅读(115)  评论(0编辑  收藏  举报