Educational Codeforces Round 86 (Rated for Div. 2)

https://codeforces.com/contest/1342

以后<=1900分的题都不想写题意题解了……除非现场做不出或者WA几个点……

毕竟感觉很难形容步骤,说得不清不楚。

A - Road To Zero

随便贪一下。

B - Binary Period

假如是同种字符,周期是1,直接输出原序列。

否则周期至少就是2,恰好可以构造这个2。

C - Yet Another Counting Problem

a和b太小了,直接暴力预处理出[1,ab]。

久了不写各种演。

int a, b, q;
int c[80005];

void TestCase() {
    scanf("%d%d%d", &a, &b, &q);
    int T = a * b;
    for(int i = 1; i <= 2 * T; ++i) {
        int c1 = i % a % b;
        int c2 = i % b % a;
        c[i] = c[i - 1] + (c1 != c2);
    }
    while(q--) {
        ll l, r;
        scanf("%lld%lld", &l, &r);
        ll ans = (r - l) / T * c[T];
        l %= T;
        r %= T;
        if(r < l)
            r += T;
        ans += c[r] - (l >= 1 ? c[l - 1] : 0);
        printf("%lld ", ans);
    }
    puts("");
    return;
}

D - Multiple Testcases

二分乱搞?但是写出二分之后,发现check的时候好像要用线段树?然后发现用了线段树之后就不需要二分了。

但是用线段树只是为了实现区间加减,然后在所有修改之后再求一次最值,这个直接打差分标记就可以了。

然后求出需要多少组case之后,有个很明显的贪心就是往剩余限制最大的case填充,但是“剩余限制”这个东西蛮复杂的。

实际上如果按从小到大的顺序填充的话,可以直接把一个元素变大。想到这一点之后,立刻就可以知道是每组case轮流填充。

用个vector统计一下,复杂度线性。

int n, k;

int a[200005];
int c[200005];

int cnta[200005];
int dif[200005];
int suffix[200005];

vector<int> vec[200005];

void TestCase() {
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        cnta[a[i]] += 1;
        dif[1] += 1;
        dif[a[i] + 1] -= 1;
    }
    for(int i = 1; i <= k; ++i)
        scanf("%d", &c[i]);
    int ans = 0, curd = 0;
    for(int i = 1; i <= k; ++i) {
        curd += dif[i];
        suffix[i] = curd;
        ans = max(ans, (suffix[i] + c[i] - 1) / c[i]);
    }
    printf("%d
", ans);
    int top = 0;
    for(int i = 1; i <= k; ++i) {
        while(cnta[i]--) {
            ++top;
            vec[top].push_back(i);
            if(top == ans)
                top = 0;
        }
    }
    for(int i = 1; i <= ans; ++i) {
        printf("%d", (int)vec[i].size());
        for(auto &v : vec[i])
            printf(" %d", v);
        printf("
");
    }
    return;
}

甚至连vector都不需要,155ms,1600KB,人生巅峰。

int n, k;
int a[200005];
int cnt[200005];

void TestCase() {
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        cnt[a[i]] += 1;
    }

    int ans = 0, curd = n;
    for(int i = 1, c; i <= k; ++i) {
        scanf("%d", &c);
        curd -= cnt[i - 1];
        ans = max(ans, (curd + c - 1) / c);
    }

    for(int i = 1, top = 0; i <= k; ++i) {
        while(cnt[i]--)
            a[++top] = i;
    }

    printf("%d
", ans);

    for(int i = 1; i <= ans; ++i) {
        int siz = 0;
        for(int j = i; j <= n; j += ans)
            ++siz;
        printf("%d", siz);
        for(int j = i; j <= n; j += ans)
            printf(" %d", a[j]);
        printf("
");
    }
    return;
}

*E - Placing Rooks

又看见组合题了。

题意:放 (n*n) 的棋盘上放 (n) 个车,攻击所有的格子,且有 (k) 对车互相攻击。求方法数。

题解:看这个 (n) 的规模看起来正解应该是迭代。 (n) 个车攻击所有的格子,那么是否必须满足“每行恰好有一个车”或者“每列恰好有一个车”其中之一呢?感觉确实是这样。一个车最多参与4对“互相攻击”(事实上根据后面的分析应该是2对),所以 (k) 太大肯定是 (0) 。先特判掉 (k=0) 的情况,此时是n的排列数。然后假如我们求出“每行恰好有一个车”的解法,只需要转置一下就是“每列恰好有一个车”的解法,且不重不漏。

那么假如上面的假设正确,当所有车排成一条直线时取得最大的合法的 (k)(k) 减少1必须移动第一行或者最后一行的车, (k) 减少2是不是会有很多玩法呢(可以分别移动第一行和最后一行的车到不同的位置,或者从中间行取出一个车),毕竟他们可以在新的位置重新互相攻击。

那么若设第i列的车的数量为 (c[i]),总的互相攻击对数为:

(sum_{i=1}^{n} calc(c[i]))

其中 (calc(x)) 函数应该是:

int calc(int x){
    if(x<=1)
        return 0;
    return (2+(x-2)*2)/2;
}

那么就要先把把k拆成若干个 (calc) 的和,然后给每一列用组合数选出一些行。

那么设 (dp[i][j][k]) 为在前 (i) 行分别放一个车,他们分别占据了 (j) 列,已有的互相攻击的对数为 (k) 的方案数,那么有转移:

(dp[i][j][k]=dp[i-1][j][k-2]+dp[i-1][j-1][k])

可惜 (n) 太大了,不然这就已经做完了。但是上式给了一个什么启发呢?确实不会。敦爷模式启动。


题解提示了下面的东西:

int calc(int x){
    return x-1;
}

枚举 (n) 个车占有的列数为 (j),则有 (n-j) 对,所以要恰好有 (k) 对,则要占有恰好 (n-k) 列。

但是还是不知道怎么统计。

原来是要容斥。


问题转化为:“把 (n) 个不同球放在 (n-k) 个不同的盒子里,每个盒子至少要有 (1) 个球。”。

“第二类斯特林数”的定义是“把 (n) 个不同球放在 (m) 个相同的盒子里,每个盒子至少要有 (1) 个球。”,给这 (m) 个盒子乘上一个排列数就得到上面要求的东西。

ll qpow(ll x, int n) {
    ll res = 1;
    if(x >= MOD)
        x %= MOD;
    while(n) {
        if(n & 1) {
            res = res * x;
            if(res >= MOD)
                res %= MOD;
        }
        x = x * x;
        if(x >= MOD)
            x %= MOD;
        n >>= 1;
    }
    return res;
}

ll S(int n, int m) {
    ll sum = 0;
    sum += qpow(m, n);
    ll fac1 = 1, fac2 = 1;
    for(int i = 1; i <= m; ++i) {
        fac1 *= (m + 1 - i);
        if(fac1 >= MOD)
            fac1 %= MOD;
        fac2 *= i;
        if(fac2 >= MOD)
            fac2 %= MOD;
        ll tmp = (i & 1) ? (MOD - 1) : (1);
        tmp *= fac1;
        if(tmp >= MOD)
            tmp %= MOD;
        tmp *=  qpow(fac2, MOD - 2);
        if(tmp >= MOD)
            tmp %= MOD;
        tmp *=  qpow(m - i, n);
        if(tmp >= MOD)
            tmp %= MOD;
        sum += tmp;
        if(sum >= MOD)
            sum -= MOD;
    }
    sum *= qpow(fac1, MOD - 2);
    if(sum >= MOD)
        sum %= MOD;
    return sum;
}

ll A(ll n, ll m) {
    ll fac1 = 1;
    for(int i = 1; i <= n; ++i) {
        fac1 *= i;
        if(fac1 >= MOD)
            fac1 %= MOD;
    }
    ll fac2 = 1;
    for(int i = 1; i <= n - m; ++i) {
        fac2 *= i;
        if(fac2 >= MOD)
            fac2 %= MOD;
    }
    return (fac1 * qpow(fac2, MOD - 2)) % MOD;
}

void TestCase() {
    int n;
    ll k;
    scanf("%d%lld", &n, &k);
    if(k > n) {
        puts("0");
        return;
    }
    if(k == 0) {
        ll fac = 1;
        for(int i = 1; i <= n; ++i) {
            fac *= i;
            if(fac >= MOD)
                fac %= MOD;
        }
        printf("%lld
", fac);
        return;
    }
    int m = n - k;
    ll fac = 1;
    for(int i = 1; i <= m; ++i) {
        fac *= i;
        if(fac >= MOD)
            fac %= MOD;
    }
    printf("%lld
", 2ll * A(n, m) * S(n, m) % MOD);
}
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12794586.html