P8438 题解
cz 过了,用的是点名被卡但是卡卡常就能冲过去的写法。
本题的最大难点在于,对洛谷评测机的效率的高度了解。
首先,不难根据题目中对于 unsigned long long 进行全程的运算,直接自然溢出啥事没有。
如果你手写去拆二进制速度是慢的。这里可以使用内建函数 __builtin_ffs(x),返回
参考代码如下:
for (int i=1;i<(1<<n);i++)
{
for (int j=__builtin_ffs(i);j;j--)
ret^=a[j];
ans^=ret*i;
}
如果你提交这个代码,你会发现在
我们考虑对这个循环进行一定的优化。我们考虑对于相邻
而对于 ret 异或上 a[1]、a[1] xor a[2]、a[1] 即可。对于 for (int j=__builtin_ffs(i);j;j--) 这层循环。可以让调用 __builtin_ffs() 函数的次数变成原来的
此外,将对 ret 和 ans 两个变量的计算进行了上述的展开,本质也会加速 CPU 的流水线调度,进一步地去优化常数。这样,你的代码就足以通过此题了。实际上这样做的常数是相当优秀的(循环展开相当于除以 __builtin_ffs() 循环的优化也大幅度减小了常数,而且本身这个循环就很难跑到
代码如下:
int n;
unsigned long long a[33],ret=0,ans;
int main()
{
n=read();
for (int i=1;i<=n;i++)
a[i]=read();
int i;
for (i=1;i+4<(1<<n);)
{
ret^=a[1];
ans^=ret*(i++);
ret^=a[1]^a[2];
ans^=ret*(i++);
ret^=a[1];
ans^=ret*(i++);
for (int j=__builtin_ffs(i);j;j--)
ret^=a[j];
ans^=ret*(i++);
}
ret^=a[1];
ans^=ret*(i++);
ret^=a[1]^a[2];
ans^=ret*(i++);
ret^=a[1];
ans^=ret*(i++);
cout << ans << endl;
return 0;
}
实际上,洛谷评测机的速度是足以支持在 1 秒内进行 unsigned long long 类型变量相乘并且自然溢出的。所以如果是小常数
这里就需要指出另外一种做法:将高低位拆分之后暴力合并去计算。这样的做法的时间复杂度也是很高的,因为它要将两个
for(i=0;i<lim1;i++)
{
for(j=0;j<lim2;j++)
ans^=(dp[i]^dp2[j])*(unsigned long long)(i+j*lim1);
}
实际上我们可以使用如下代码获得洛谷评测机的 CPU 型号:
#include <stdint.h>
#include <iostream>
#include <cpuid.h>
static void cpuid(uint32_t func, uint32_t sub, uint32_t data[4]) {
__cpuid_count(func, sub, data[0], data[1], data[2], data[3]);
}
int main() {
uint32_t data[4];
char str[48];
for(int i = 0; i < 3; ++i) {
cpuid(0x80000002 + i, 0, data);
for(int j = 0; j < 4; ++j)
reinterpret_cast<uint32_t*>(str)[i * 4 + j] = data[j];
}
std::cout << str;
}
可得洛谷 CPU 型号为 Intel(R) Xeon(R) Platinum 8369HC CPU @ 3.30GHz。然而由于这个是阿里云定制 CPU,其实很难查到它的参数数据。
但是与其同一代的 CPU Intel(R) Xeon(R) Platinum 8360HC CPU @ 3.00GHz 的 Cache 大小是 33MB,而有着 24 个核心,也就是说每个核心有着 1.375MB 的高速缓存,足以存下两个大小为 65536 的 unsigned long long 数组。当这些数组进入高速缓存(实际上应该是 L3 cache)之后,其读写效率是显著快于在内存中的读写的(快 4 倍左右)。此外,合并的过程做的都是连续的内存访问,有着很好的空间局部性,也减少了常数。因而,它在