码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2020-04-05 10:03:35      阅读:116      评论:0      收藏:0      [点我收藏+]

标签:def   mis   情况   ati   元素   bool   必须   printf   受限   

题目链接: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], " \n"[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\n", sum);
}

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

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

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

E - Drazil Likes Heap

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

标签:def   mis   情况   ati   元素   bool   必须   printf   受限   

原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12635707.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!