题解:P2025 Dirichlet 半在线卷积
DeepSkyCore · · 题解
此题解非正解,复杂度
显然有一个非常简单的做法:先筛出来
不过如果真的就这么写,自然是过不去的。这里提供一个常数非常小的写法,似乎在这题数据范围下比正解还快很多。
先想想这个东西为什么会慢。算一算实际的转移次数,甚至都不到
首先考虑分块转移,也就是从小到大枚举
constexpr int B = 2e6;
int n; cin>>n;
vector<u32> f(n+1), lst(n+1, 2);
f[1] = 1;
for(int l = 1, r = min(B, n); l <= n; l = r+1, r = min(l + B - 1, n)){
for(u32 l0 = 1, r0, k; ; l0 = r0+1){
k = r / l0, r0 = r / k;
if(k == 1) break;
rep(i,l0,r0){
while(lst[i] <= k){
f[i*lst[i]] += f[i] * phi[lst[i]];
lst[i]++;
}
}
}
}
u32 ans = 0;
rep(i,1,n){
ans ^= f[i];
}
cout<< ans <<'\n';
不过就算这么搞,我们还是要多次扫描一遍 200MB 的数组,同时还需要在大概 8MB 的数组做随机访问,还能不能优化呢?
考虑一个非常简单的结论:设
这么搞的话不需要多次扫 200MB 的数组,同时随机访问的内存也更密集。这样就跑得很快了,大概只需要 1.3s。同样给出参考实现:
constexpr int B = 65536
int n; cin>>n;
vector<u32> f(n+1);
f[1] = 1;
int l = 1, r = min(n, B);
rep(i,1,r/2){
for(int j=2; j <= r/i; j++){
f[j*i] += f[i] * phi[j];
}
}
l = r+1, r = min(l + B - 1, n);
for(; l <= n; l = r+1, r = min(l + B - 1, n)){
rep(j,l,r){
f[j] += phi[j];
}
rep(i,2,B){
rep(j, max(i, (l-1)/i+1), r/i){
f[i*j] += f[i]*phi[j];
if(i != j) f[i*j] += phi[i]*f[j];
}
}
}
u32 ans = 0;
rep(i,1,n){
ans ^= f[i];
}
cout<< ans <<'\n';
在这题中,由于函数固定,我们还可以结合分段打表。
同时,上述解法的常数非常小。可以看出即使上面的参考实现仍有优化空间,这段代码仍然能在 P5495 取得优于一般
虽然复杂度更劣,但是由于好写好调常数小,暴力仍然是一个不错的选择。希望大家能够学会这种写法。