• Codeforces Round #631 (Div. 2)


    题目链接: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

    看了这个题解觉得挺巧妙的,不过自己当时可能是没有这个勇气去做了吧。

    题意:定义一种 (h) 阶的“奇怪堆”,堆中没有相同的元素。这个奇怪堆首先是一个大顶堆,其次是一棵高度为 (h) 的满二叉树(有 (2^h-1) 个节点的完全二叉树)。然后定义一种“奇怪堆”的“奇怪删除”,这个删除不是先把要删除的元素换到末尾,而是直接原地删除(若这个点没有儿子,则直接删除,否则用其大儿子代替之,然后递归给大儿子),所以删除结束之后一般剩下的就不再是堆了。但是题目要求给出一个删除的序列,使得删除完这些序列之后,剩下的恰好是一个 (g) 阶的“奇怪堆”,且这个“奇怪堆”的剩余元素总和最小。

    题解:首先证明一个结论:假如删除一个点使得剩余的高度不少于 (g) ,则删除这个节点比删除这个节点的大儿子更优。这个其实很好想,无论是删除这个节点还是删除这个节点的大儿子,都是多出一个空位,且对树的形态的改变相同(无论是这个节点还是这个节点的大儿子占着这个位置,都会比小儿子以及小儿子的所有后代都要大),那么删除这个节点会使得总和变得更小。把某个节点到其大儿子,以及大儿子到其大孙子,以及……的链称为“大链”,容易看到上面的结论就是不断吐出“大链”的过程,注意吐到一半的时候有可能会切换到原本的小儿子变成“大链”。否则,这个节点到大儿子以及递归下去的“大链”已经是高度恰好是 (g) ,这个时候不可以再对“大链”进行操作,需要递归给两个儿子,让他们自己寻找自己的新的“大链”。

    #define ls (2 * id)
    #define rs (2 * id + 1)
    
    int h, g;
    int a[(1 << 21) + 5];
    int ans[(1 << 20) + 5], atop;
    
    int dep2(int id) {
        if(a[ls] == 0 && a[rs] == 0)
            return 1;
        if(a[ls] > a[rs])
            return dep2(ls) + 1;
        else
            return dep2(rs) + 1;
    }
    
    bool CheckRemove(int id, int dep) {
        return dep + dep2(id) - 1 > g;
    }
    
    void Maintain(int id, bool flag = false) {
        if(flag)
            ans[++atop] = id;
        if(a[ls] == 0 && a[rs] == 0) {
            a[id] = 0;
            return;
        }
        if(a[ls] > a[rs]) {
            a[id] = a[ls];
            Maintain(ls);
        } else {
            a[id] = a[rs];
            Maintain(rs);
        }
        return;
    }
    
    void solve(int id, int dep) {
        if(a[id] == 0)
            return;
        while(CheckRemove(id, dep))
            Maintain(id, true);
        solve(ls, dep + 1);
        solve(rs, dep + 1);
        return;
    }
    
    void TestCase() {
        scanf("%d%d", &h, &g);
        int n = (1 << h) - 1;
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        atop = 0;
        solve(1, 1);
        int m = (1 << g) - 1;
        ll sum = 0;
        for(int i = 1; i <= m; ++i)
            sum += a[i];
        printf("%lld
    ", sum);
        for(int i = 1; i <= atop; ++i)
            printf("%d%c", ans[i], " 
    "[i == atop]);
        for(int i = 1; i <= n; ++i)
            a[i] = 0;
        return;
    }
    

    参考资料:https://www.cnblogs.com/axiomofchoice/p/12632461.html

  • 相关阅读:
    iOS Sprite Kit最新特性Physics Field虚拟物理场Swift測试
    java中接口的定义与实现
    2014年百度之星程序设计大赛
    MyEclipse7.0破解下载
    C++中的explicitkeyword
    抽象工厂模式
    《Head First 设计模式》学习笔记——策略模型
    MFC原创:三层架构01(人事管理系统)DAL
    Design Pattern Singleton 单一模式
    C学习笔记之预处理指令
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12635707.html
Copyright © 2020-2023  润新知