• 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);
    }
    
  • 相关阅读:
    js 变速动画函数
    js 获取滚动条事件
    js 获取任意一个元素的任意一个样式属性的值
    js 三大事件(鼠标.键盘.浏览器)
    关于数组的一些方法
    mvc获取时间戳
    html5响应式设置<meta>
    jq遍历url判断是否为当前页面然后给导航上色
    mvc正则@符号js报错解决办法
    无法在提交表单前通过ajax验证解决办法
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12794586.html
Copyright © 2020-2023  润新知