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不掉!