Codeforces Round #631 (Div. 2) - Thanks, Denis aramis Shitov!

Posted kisekipurin2019

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces Round #631 (Div. 2) - Thanks, Denis aramis Shitov!相关的知识,希望对你有一定的参考价值。

题目链接:https://codeforces.com/contest/1330

A - Dreamoon and Ranking Collection

随便弄弄。

B - Dreamoon Likes Permutations

题意:给一串n个数字,范围在[1,n-1],求有多少种方法,把它断成前后两段排列。

题解:要断成两段排列,首先必须要同一个数字最多出现2次。在满足这个条件的情况下,某段i个数字的前缀形成一个排列的充要条件是:1、i个数字互不重复;2、最大值是i。这样搞一搞前缀和后缀,然后数一数前缀和后缀同时成立的方案。

C - Dreamoon Likes Permutations

题意:给 (m) 个长度线段A,第 (i) 条线段长度为 (li) ,要求把这些线段A([1,m]) 的顺序依次覆盖在线段B ([1,n]) 上(不能左右越界),后面覆盖的线段A会把前面的线段A遮住。要求整条线段B ([1,n]) 的每段都至少被一条线段A覆盖,且每条线段A至少露出1段(没有被其他线段A完全遮住)。

题解:一开始想了很多奇奇怪怪的构造,但是觉得很不自然。而且这个长度也没有什么升降序的规律。然后想到了一种容易想到的不可构造情形:线段A的总长度不够
(n) 。然后贪心的方法是:每条线段都依次露出最左的一小段,最后会覆盖住线段B的左半侧,假如还没有把线段B覆盖完,那么把最后一条线段A平移到最右侧,这样既不会对前面的线段造成更多的遮挡,也不会浪费长度,移动完之后可以把最后一条线段整条忽视掉,尝试把倒数第二条线段右移,直到把整条线段B都覆盖。那么是否还有一种不可构造情形是:每条线段A都露出1段之后,最长的线段A不能超过 (m-1) 呢?这个是错的,前面的线段A可以更长,例如第1条线段A可以长到 (n) ,第2条线段A可以长到 (n-1) ,以此类推。

int l[100005];
int ans[100005];

void TestCase() {
    int n, m;
    scanf("%d%d", &n, &m);
    ll sum = 0;
    bool suc = 1;
    for(int i = 1; i <= m; ++i) {
        scanf("%d", &l[i]);
        sum += l[i];
        if(l[i] + i - 1 > n)
            suc = 0;
    }
    if(suc == 0 || sum < n) {
        puts("-1");
        return;
    }
    for(int i = 1; i <= m; ++i)
        ans[i] = i;
    int R = n + 1;
    for(int i = m; i >= 2; --i) {
        if(R - l[i] > ans[i - 1]) {
            ans[i] = R - l[i];
            R -= l[i];
        } else
            break;
    }
    for(int i = 1; i <= m; ++i)
        printf("%d%c", ans[i], " 
"[i == m]);
    return;
}

*D - Dreamoon Likes Sequences

一个有明显dp特征的计数题。

题意:给出两个比较大的整数 (d,m) ,求满足下面现在条件的数列a的数量模 (m) 的值,模 (m) 是为了不希望大家OEIS得太爽。

1、数列a至少有一个元素
2、数列a严格升序,取值范围为 ([1,d])
3、设数列b为:
(;;;;;; b_1=a_1)
(;;;;;; b_i=b_{i-1} oplus a_i (i geq 2))
4、数列b严格升序

题解:其实仔细算出来之后发现应该还是可以OEIS得很爽,毕竟只和 (d) 有关。

首先观察这个奇奇怪怪的条件,异或之后要升序?那么很显然 (a_i) 的最高位1不能与 (a_{i-1}) 的最高位1相同,否则将导致 (b_i) 失去这个最高位。同时由于限制2, (a_i) 的最高位1不能低于 (a_{i-1}) 的最高位1,所以 (a_i) 的最高位1只能高于 (a_{i-1}) 的最高位1,且不能导致超过 (d)

那么既然选好了这个最高位1,后面的位当然就是随便选了,所以就乘上某个2的幂次。

在忽略与 (d) 拥有最高位1的情况时,非常好办,记 (d) 的最高位1位于第 (logd) 位,记 (dp[i]) 表示“最后一个数的最高位是低位的 (i-1) 位的种类”,特殊的记 (dp[0]=1) 表示“空数列 ([]) ”也是1种。

那么 (dp[1]=(dp[0])*1) ,因为最高位是1的就只有1种,就是 ([1])

那么 (dp[2]=(dp[1]+dp[0])*2) ,因为最高位是2的,有2种,且他们都可以跟在最高位是0的或者最高位是1的后面: ([1,2],[1,3],[2],[3])

那么 (dp[3]=(dp[2]+dp[1]+dp[0])*4) ,因为最高位是4的,有4种,且他们都可以跟在最高位是0的或者最高位是1或者最高位是2的后面: ([1,2,4],[2,4],[3,4],[1,2,5],[2,5],[3,5],[1,2,6],[2,6],[3,6],[1,2,7],[2,7],[3,7],[4],[5],[6],[7])

麻烦事在于找类似数位dp的时候的 (d) 的限制。不过仔细想想没有那么复杂,首先,最高位与 (d) 相同,是没有选择的余地的,直接continue。否则,若 (d) 的这一位是0,也是没有选择的余地的,直接continue。否则, (d) 的这一位是1,若这一位选择填0,那么后面的选择(若还有得选)将不受限制,这个选法也是某个2的幂次。否则, (d) 的这一位是1,且这一位选择填1,则继续迭代下去,直接continue。这种算法统计了在 (d) 的某位1填了0的种类,补上和 (d) 完全相等的这一种,就是 (dp[logd])最后一个元素的选择方案,用来替代前面的那些2的幂次,还是要乘上前面的 (dp) 值的和。

最后,把所有的 (dp) 值加起来。

ll dp[80];

void TestCase() {
    ll d, m;
    scanf("%lld%lld", &d, &m);
    ll cd = d;
    int logd = 0;
    while(cd) {
        ++logd;
        cd >>= 1;
    }
    memset(dp, 0, sizeof(dp));
    dp[0] = 1;
    for(int i = 1; i <= logd; ++i) {
        if(i < logd) {
            //最高为取1,后面i-1位任取
            ll cnt = (1ll << (i - 1)) % m;
            ll prefix = 0;
            for(int j = i - 1; j >= 0; --j)
                prefix += dp[j];
            prefix %= m;
            dp[i] = prefix * (1ll << (i - 1)) % m;
        } else {
            //选d本身,是1种选法
            ll cnt = 1;
            for(int j = logd - 1; j >= 0; --j) {
                //最高位必定取1
                if(j == logd - 1)
                    continue;
                //不是最高位,且d这一位是1
                if((1ll << j)&d)
                    //这一位选择0,后面的任选
                    cnt += (1ll << j) % m;
                //这一位依然受限
            }
            ll prefix = 0;
            for(int j = i - 1; j >= 0; --j)
                prefix += dp[j];
            prefix %= m;
            dp[i] = prefix * cnt % m;
        }
    }
    ll sum = 0;
    for(int i = 1; i <= logd; ++i)
        sum += dp[i];
    sum %= m;
    printf("%lld
", sum);
}

因为担心溢出FST重交了一发,但是事实上这个并不会溢出,虽然做了很多次加法,模之前做乘法有溢出的可能,但是未必参与加法的结果有这么大(因为这个数值几乎是只与位数有关的,而且每次扩大一个很大的倍数,非常容易与5e8之类大数的擦肩而过)。

不过反正上不了橙色,不亏。

启发:注意处理空数列的情况,以及这种求<=x的满足某种条件的数的个数,可以从数位受限去想,具体的做法是:若这一位选择了比较小的数,则后面的位就会解除<=x这个条件,只受到其他条件的制约,否则就继续往下处理,最后考虑是否要补上x的情况。

E - Drazil Likes Heap







以上是关于Codeforces Round #631 (Div. 2) - Thanks, Denis aramis Shitov!的主要内容,如果未能解决你的问题,请参考以下文章

Codeforces Round #344 (Div. 2) 631 C. Report (单调栈)

Codeforces Round #631 (Div. 2) B. Dreamoon Likes Permutations(排列组合)

Codeforces Round #631 (Div. 2) - Thanks, Denis aramis Shitov!

Codeforces Round #631 (Div. 2) - Thanks, Denis aramis Shitov!

Codeforces Round #631 (Div. 2) Dreamoon Likes Sequences

Codeforces Round #631 (Div. 2) C. Dreamoon Likes Coloring(贪心好题/意识流题解)