• Codeforces Round #647 (Div. 2)


    https://codeforces.com/contest/1362

    A - Johnny and Ancient Computer

    无语

    B - Johnny and His Hobbies

    暴力

    *C - Johnny and Another Rating Drop

    找规律

    题意:给一个 (n) ,求 ([0,n]) 的每个数字的二进制表示中,相邻两个数字的二进制表示的汉明距离和。

    例如,当 (n=5) 时,为:

    000
    001
    010
    011
    100
    101
    

    汉明距离和为:1+2+1+3+1=8

    题解:打个表,看起来是有这样的规律:

    1
    2 1
    3 1 2 1
    4 1 2 1 3 1 2 1
    

    打更长的表可以验证出,第 (i) 行拥有 (2^i-1) 个数字,且第一个数字为 (i) ,后面的数字为前面各行的复制。

    所以设第 (i) 行的和为 (dp[i]) 则:

    (dp[1]=1)
    (dp[i]=i+sum_{j=1}{i=1}dp[j])

    这个用个前缀和就可以转移出来,或者直接暴力,因为总共也就最多60多行。

    然后写个奇奇怪怪的函数递归下去。

    例如 (n=15) ,则为 (dp[1]+dp[2]+dp[3]+dp[4]) ,若 (n=14) ,则为 (dp[1]+dp[2]+dp[3]+4+dp[1]+dp[2]+3+dp[1]+2) ,发现什么规律?若不是
    (2^i-1) ,则总是把前面的dp全部加进来,然后若第 (i) 行没有全部加进来,就加入一个 (i) 并递归到下一层。例如 (sum(14)=dp[1]+dp[2]+dp[3]+4+dp[1]+dp[2]+3+dp[1]+2) ,其实就是 (sum(14)=dp[1]+dp[2]+dp[3]+4+sum(6))

    ull dp_calc[105];
     
    void InitCase() {
        memset(dp_calc, -1, sizeof(dp_calc));
        return;
    }
     
    ull calc(int B) {
        if(dp_calc[B] == -1) {
            if(B == 1)
                dp_calc[B] = 1;
            else
                dp_calc[B] = (1llu + 2llu * calc(B - 1));
        }
        return dp_calc[B];
    }
     
    ull calc_sum(ull len) {
        if(len <= 1llu)
            return len;
        ull tmplen = 1llu;
        int B = 1;
        while((tmplen << 1) <= len) {
            tmplen <<= 1;
            ++B;
        }
        return calc(B) + calc_sum(len - tmplen);
    }
    
    void TestCase() {
        ull n;
        scanf("%llu",&n);
        printf("%llu
    ",calc_sum(n));
        return;
    }
    

    但这题其实可以分开各个位去考虑。很容易发现下式:因为 (2^i) 位是每 (2^i) 改变一次。

    void TestCase() {
        ull n;
        scanf("%llu", &n);
        ull ans = 0;
        for(ull i = 1; i <= n; i <<= 1)
            ans += n / i;
        printf("%llu
    ", ans);
        return;
    }
    

    D - Johnny and Contribution

    模拟

    *E - Johnny and Grandmaster

    题意:给一个数字 (p) ,和 (n) 个指数 (k_i) ,真正的数组是 (p^{k_i}) ,要求分成两个组使得他们的差的绝对值最小,求这个绝对值。

    题解:首先说一个没有用的观察:相当于给他们安排正号和负号使得和的绝对值最小。直接从大到小排序,然后“轮流”分配,若两个组当前相等,则分配给第一组,否则第一组一定比第二组多,这时分配给第二组。记录一个量 (dif) ,表示第一组比第二组多的值需要多少个 (cur) 才能拉平, (cur) 就是从大到小分配到了哪一个,容易发现这个算法下保持第一组大于等于第二组,那么每次 (cur) 变化的时候同步扩大 (dif) ,这里有最后一个问题就是溢出,其实只要 (dif) 超过剩余元素的数量就把剩下的全部塞给第二组就可以了。最后注意TLE的原因是因为 (p=1) 会导致,每个TestCase都会跑满 (max(k_i)-min(k_i)) ,因为若 (pgeq 2) 则只需要 (log_p(max(k_i)-min(k_i)))

    提示:这题确实需要优化!

    下面的代码是可以AC的:

    int n, p;
    int k[1000005];
    int pk[1000005];
    
    void TestCase() {
        scanf("%d%d", &n, &p);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &k[i]);
        if(p == 1) {
            printf("%d
    ", n % 2);
            return;
        }
        sort(k + 1, k + 1 + n, greater<int>());
        for(int i = 1; i <= n; ++i) {
            if(i == 1 || k[i] != k[i - 1])
                pk[i] = qpow(p, k[i]);
            else
                pk[i] = pk[i - 1];
        }
        ll dif = 0, ans = 0;
        for(int i = 1; i <= n; ++i) {
            if(dif == 0) {
                dif += 1;
                ans += pk[i];
            } else {
                int dk = (i == 1) ? 0 : k[i - 1] - k[i];
                while(dk--) {
                    dif *= p;
                    if(dif >= (n - i + 1)) {
                        for(int j = i; j <= n; ++j)
                            ans -= pk[j];
                        ans = ((ans % MOD) + MOD) % MOD;
                        printf("%lld
    ", ans);
                        return;
                    }
                }
                dif -= 1;
                ans -= pk[i];
            }
        }
        ans = ((ans % MOD) + MOD) % MOD;
        printf("%lld
    ", ans);
        return;
    }
    

    但是这个会TLE:

    int n, p;
    int k[1000005];
    int pk[1000005];
     
    void TestCase() {
        scanf("%d%d", &n, &p);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &k[i]);
        if(p == 1) {
            printf("%d
    ", n % 2);
            return;
        }
        sort(k + 1, k + 1 + n, greater<int>());
        for(int i = 1; i <= n; ++i) {
            if(i == 1 || k[i] != k[i - 1])
                pk[i] = qpow(p, k[i]);
            else
                pk[i] = pk[i - 1];
        }
        ll dif = 0, ans = 0;
        for(int i = 1; i <= n; ++i) {
            int dk = (i == 1) ? 0 : k[i - 1] - k[i];
            while(dk--) {
                dif *= p;
                if(dif >= (n - i + 1)) {
                    for(int j = i; j <= n; ++j)
                        ans -= pk[j];
                    ans = ((ans % MOD) + MOD) % MOD;
                    printf("%lld
    ", ans);
                    return;
                }
            }
            if(dif == 0) {
                dif += 1;
                ans += pk[i];
            } else {
                dif -= 1;
                ans -= pk[i];
            }
        }
        ans = ((ans % MOD) + MOD) % MOD;
        printf("%lld
    ", ans);
        return;
    }
    

    但是感觉还是不太对劲。假如照葫芦画瓢,仿照TLE的那组数据做一个。就会使得上面的优先判断 (dif=0) 的优化无效。

    100000
    2 2
    1000000 1
    2 2
    1000000 1
    2 2
    1000000 1
    ...
    

    但是却hack不掉!

  • 相关阅读:
    读写配置文件app.config
    UML类图
    我见到James Rumbaugh了!
    获取数据库中的所有表
    通过DataTable获得表的主键
    用例的本质
    用例图
    使用SQLDMO中“接口SQLDMO.Namelist 的 QueryInterface 失败”异常的解决方法
    类如何与界面绑定
    C#使用指针
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/13061169.html
Copyright © 2020-2023  润新知