Codeforces Round #714 (Div. 2) 游记

题目质量棒极了!!!乍一看很不可做,仔细想想问题就迎刃而解,没有一道是秒杀的。

A

求一个 nn 的排列使有 kk 个山峰。

先按 1n1 \rightarrow n 的顺序排好,然后 2i+12i+12i+22i+2 交换就可以产生一个山峰,而这种方案肯定能尽可能多的产生山峰。

#include <cstdio>
#include <algorithm>
int T, n, k, a[105];
int main() {
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &n, &k);
        if(k > (n+1)/2-1) {
            puts("-1");
            continue;
        }
        for(int i = 1; i <= n; i++) a[i] = i;
        for(int i = 2, cnt = 0; i <= n && cnt < k; i += 2, cnt += 1)
            std::swap(a[i], a[i+1]);
        for(int i = 1; i <= n; i++) printf("%d ", a[i]);
        puts("");
    }
    return 0;
}

B

一列数,从任意一点断开,若左边的全部 and 起来和右边的全部 and 起来都一样就说它是好的,问将输入的数列任意打乱有多少个是好的。

一看是没有什么思路的。

仔细想想可以发现,那些哪一位其它数有一而它这一位是 00 的都得放在前后,不然的话在它前面断开就会一边有这样一位没有边为 00 而另一边变为 00 了, 不能称为好的序列, 所以这样的选俩放首尾,其它的中间随便排就可以了。

代码还是有些细节的。

#include <cstdio>
#include <cstring>
#define int long long
const int N = 200005, p = 1000000007;
int a[N], T, n;
bool vis[N];
signed main() {
    scanf("%lld", &T);
    while(T--) {
        scanf("%lld", &n);
        for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
        memset(vis, 0, sizeof vis);
        bool twt = 0, did = 0;
        for(int i = 0; i <= 30; i++) {
            bool flag = 0, tw = 0;
            for(int j = 1; j <= n; j++) 
                if((1ll << i) & a[j]) tw = 1;
                else flag = 1;
            flag = flag && tw;
            if(!flag) {
                for(int j = 1; j <= n; j++) 
                    if(did && vis[j] == 0) vis[j] = 0;
                    else vis[j] = 1;
                continue;
            }
            for(int j = 1; j <= n; j++)
                if(((1ll << i) & a[j]) == 0ll) {
                    if(did && vis[j] == 0) vis[j] = 0;
                    else vis[j] = 1;
                }
                else vis[j] = 0;
            did = 1;
            if(twt) break;
        }
        if(twt) {
            puts("0");
            continue;
        }
        int cnt = 0;
        for(int j = 1; j <= n; j++) cnt += vis[j];
        int ans = cnt * (cnt-1) % p;
        int tans = 1;
        for(int i = 1; i <= n-2; i++) tans = tans * i % p;
        ans = ans * tans % p;
        printf("%lld\n", ans);
    }
    return 0;
}

C

把一个整数中每一个位都变为其加上一的数字, 9 变为 10, 问变 mm 次后数字长度是多少。

可怜的孩子没看清数据范围,看成保证所有的 mm 和不超过 2×1052 \times 10^5, 以为这是一个简单 dp, f[i][j]f[i][j] 表示 ii 次后 jj 出现了几次,最后 f[m][j]\sum f[m][j] 就是答案,然后顺利 T 飞。

不过这样的 DP 还有没有救呢?枚举几个小样例发现每个数都是独立变化的,既然独立变化,那么分开考虑,发现 10d10-d 次后都变成 10 继续变换了,那么只要能快速处理出 10 的变化就可以快速得出所有的变化。

一开始我一直想着有啥数学方法可做,后来一看,直接预处理一遍把所有次数以后的变换处理完不就得了。

#include <cstdio>
#include <cstring>
#define int long long
const int p = 1000000007;
int T, x, m, f[200005][10];
int get(int x) {
    if(x < 0) return 1;
    int an = 0;
    for(int i = 0; i <= 9; i++) an = (an + f[x][i]) % p;
    return an;
}
signed main() {
    scanf("%lld", &T);
    f[0][1] = f[0][0] = 1;
    for(int i = 1; i <= 200000; i++) {
        for(int j = 9; j >= 1; j--) f[i][j] = f[i-1][j-1];
        f[i][0] = 0;
        f[i][0] = (f[i][0] + f[i-1][9]) % p;
        f[i][1] = (f[i][1] + f[i-1][9]) % p;
    }
    while(T--) {
        scanf("%lld%lld", &x, &m);
        int ans = 0;
        while(x) {
            int d = x % 10;
            ans = (ans + get(m - (10-d))) % p;
            x /= 10;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

本想着上 expert, 结果卡在 1596\color{#5ac5ac}1596 ! 太让我失望了,要是看清题目,那 5050 分不扣说不定就上蓝了。