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());
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());
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不掉!