Codeforces Round #647 (Div. 2) - Thanks, Algo Muse!

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\n",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\n", 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)\) ,因为若 \(p\geq 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", 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\n", ans);
                    return;
                }
            }
            dif -= 1;
            ans -= pk[i];
        }
    }
    ans = ((ans % MOD) + MOD) % MOD;
    printf("%lld\n", 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", 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\n", ans);
                return;
            }
        }
        if(dif == 0) {
            dif += 1;
            ans += pk[i];
        } else {
            dif -= 1;
            ans -= pk[i];
        }
    }
    ans = ((ans % MOD) + MOD) % MOD;
    printf("%lld\n", ans);
    return;
}

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

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

但是却hack不掉!

posted @ 2020-06-07 17:01  KisekiPurin2019  阅读(234)  评论(0编辑  收藏  举报