P4604 [WC2017] 挑战 题解
久仰久仰。
subtask 1
首先用快排显然会超时。
所以用基数排序。
然后基数排序的复杂度是
那你肯定觉得
一般情况下确实是这样的。
但是你也不想想这是啥题目?
你选的基数如果大于
然后
然后不用其他的任何优化就可以 AC 这个 subtask 了,可见 Cache 的优化多么巨大。
排序部分代码:
unsigned cnt[256];
#define sortarr(a , b , n , byte){\
memset(cnt , 0 , sizeof(cnt));\
for(int i = n - 1;i >= 0;i--){\
cnt[(a[i] >> byte) & 255]++;\
}\
for(int i = 1;i < 256;i++) cnt[i] += cnt[i - 1];\
for(int i = n - 1;i >= 0;i--){\
b[--cnt[(a[i] >> byte) & 255]] = a[i];\
}\
}
subtask 2
首先不考虑数据结构维护,对于某些数据你不管是啥数据结构应该都是维护不了的。
然后暴力非常容易写出:
#define solve(n , q , l , r , x , y , len , anss){\
int check[3][3] = {{0 , 1 , 0} , {0 , 0 , 1} , {1 , 0 , 0}};\
int i;\
for(i = 0;i < n;i++){\
l[i] ^= 48;\
r[i] ^= 48;\
}\
for(i = 0;i < q;i++){\
int ans = 0 , j;\
for(j = 0;j < len[i];j++){\
ans += check[l[x[i] + j]][r[y[i] + j]];\
}\
anss[i] = ans;\
}\
}
虽然这东西也能过
然后我们用最无脑的卡常方法——循环展开来卡常。
可能有些同学并不知道循环展开是啥,这里说一下。
一个最简单的例子:
输入一个数
最朴(la)素(ji)的写法:
inline int getsum(int n){
int sum = 0;
for(int i = 1;i <= n;i++){
sum += i;
}
return sum;
}
循环展开后的写法:
inline int getsum(int n){
int sum = 0 , sum1 = 0 , sum2 = 0 , sum3 = 0 , sum4 = 0 , sum5 = 0 , sum6 = 0 , sum7 = 0 , i;
for(i = 1;i + 8 <= n;i += 8){
sum += i;
sum1 += i + 1;
sum2 += i + 2;
sum3 += i + 3;
sum4 += i + 4;
sum5 += i + 5;
sum6 += i + 6;
sum7 += i + 7;
}
for(;i <= n;i++){
sum += i;
}
return sum + sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7;
}
错误的循环展开:
inline int getsum(int n){
int sum = 0 , i;
for(i = 1;i + 8 <= n;i += 8){
sum += i;
sum += i + 1;
sum += i + 2;
sum += i + 3;
sum += i + 4;
sum += i + 5;
sum += i + 6;
sum += i + 7;
}
for(;i <= n;i++){
sum += i;
}
return sum;
}
但是这个也有优化作用。
那接下来,我们引出一个问题:
为什么循环展开能减小常数?
因为大多数时候 CPU 是有多个核心的,而我们评测的时候基本上只用了一个。多浪费啊!所以我们用循环展开来让 CPU 进行 CPU 并发,减小常数。
所以我们使用了循环展开后,这一部分的时间就会变的很少。
此部分代码:
#define solve(n , q , l , r , x , y , len , anss){\
int check[3][3] = {{0 , 1 , 0} , {0 , 0 , 1} , {1 , 0 , 0}};\
int i;\
for(i = 0;i + 8 < n;i += 8){\
l[i] ^= 48;\
r[i] ^= 48;\
l[i + 1] ^= 48;\
r[i + 1] ^= 48;\
l[i + 2] ^= 48;\
r[i + 2] ^= 48;\
l[i + 3] ^= 48;\
r[i + 3] ^= 48;\
l[i + 4] ^= 48;\
r[i + 4] ^= 48;\
l[i + 5] ^= 48;\
r[i + 5] ^= 48;\
l[i + 6] ^= 48;\
r[i + 6] ^= 48;\
l[i + 7] ^= 48;\
r[i + 7] ^= 48;\
}\
for(;i < n;i++){\
l[i] ^= 48;\
r[i] ^= 48;\
}\
for(int i = 0;i < q;i++){\
int ans = 0 , ans1 = 0 , ans2 = 0 , ans3 = 0 , ans4 = 0 , ans5 = 0 , ans6 = 0 , ans7 = 0 , j;\
for(j = 0;j + 8 < len[i];j += 8){\
ans += check[l[x[i] + j]][r[y[i] + j]];\
ans1 += check[l[x[i] + j + 1]][r[y[i] + j + 1]];\
ans2 += check[l[x[i] + j + 2]][r[y[i] + j + 2]];\
ans3 += check[l[x[i] + j + 3]][r[y[i] + j + 3]];\
ans4 += check[l[x[i] + j + 4]][r[y[i] + j + 4]];\
ans5 += check[l[x[i] + j + 5]][r[y[i] + j + 5]];\
ans6 += check[l[x[i] + j + 6]][r[y[i] + j + 6]];\
ans7 += check[l[x[i] + j + 7]][r[y[i] + j + 7]];\
}\
for(;j < len[i];j++){\
ans += check[l[x[i] + j]][r[y[i] + j]];\
}\
anss[i] = ans + ans1 + ans2 + ans3 + ans4 + ans5 + ans6 + ans7;\
}\
}
subtask 3
显然是一个 dp。
具体实现的时候就是扫一遍,每遇到一个 ? 就把他左边的方案数和右边的方案数加起来。
但是显然过不了。
发现可达到的数的下标的奇偶性是和外层循环的奇偶性是一样的,因为一对匹配的左右括号占了两个位置。
再加个指针优化寻址就能过。
代码:
u32 ans[600000];
int solvee(int n , char *s){
u32 *dp = ans + 270000;
dp[0] = 1;
for(int i = n - 1;i >= 0;i--){
if(s[i] == '('){
++dp;
}
else if(s[i] == ')'){
--dp;
}
else{
int up = min(n - i , i) , lst = 0;
for(int j = i % 2;j <= up;j += 2){
dp[j] = dp[j - 1] + dp[j + 1];
}
}
dp[-1] = 0;
}
return dp[0];
}
最后把代码结合起来,就可以 AC 了。