原题网址:http://acm.hdu.edu.cn/showproblem.php?pid=4947
原题大意:对于一组初始为0的数列,进行两种操作:对所有x满足
gcd(x,n)=d
的
ax
增加v;计算
∑xi=1ai
。
对于条件一,其等价于对每个数增加
[gcd(x,n)=d]∗v
,而
[gcd(x,n)=d]∗v=[gcd(x/d,n/d)=1]∗v=∑i|gcd(x/d,n/d)μ(i)∗v=∑i|nd and i∗d|xμ(i)∗v
,构造数组sum[],满足
a[i]=∑j|isum[j]
操作一即为对所有满足条件的i,对sum[i*d]加
μ(i)∗v
,操作二求和则对于前x个
ai
的和,sum[i]被计算的次数为
xi
次,这块可以用分块思想,同时用树状数组维护。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
#define ll long long
const int N = 200010;
bool check[N];
int prime[N];
int mu[N];//莫比乌斯函数
ll sum[N];
ll ans, temp, lasttemp, pos;
vector<int> fac[N];//因子
ll getsum(int x){
ll ans = 0;
for (; x; x -= x&-x)
ans += sum[x];
return ans;
}
void add(int x, int v){
for (; x<N; x += x&-x)
sum[x] += v;
}
void Moblus(){//线性筛法求莫比乌斯函数
memset(check, false, sizeof(check));
mu[1] = 1;
int tot = 0;
for (int i = 2; i <= N; i++){
if (!check[i]){
prime[tot++] = i;
mu[i] = -1;
}
for (int j = 0; j < tot; j++){
if (i*prime[j] > N) break;
check[i*prime[j]] = true;
if (i%prime[j] == 0){
mu[i*prime[j]] = 0;
break;
}
else{
mu[i*prime[j]] = -mu[i];
}
}
}
}
int main(){
int l, m, cas = 0;
Moblus();
for (int i = 1; i < N; i++)
for (int j = i; j < N; j += i)
fac[j].push_back(i);
while (scanf("%d%d", &l, &m), l, m){
cas++;
printf("Case #%d:\n", cas);
memset(sum, 0, sizeof(sum));
while (m--){
int t, n, d, v, x;
scanf("%d", &t);
if (t == 1){
scanf("%d%d%d", &n, &d, &v);
if (n%d) continue;
n /= d;
for (int i = 0; i < fac[n].size(); i++){
x = fac[n][i];
add(x*d, mu[x] * v);
}
}
else{
scanf("%d", &x);
ans = temp = 0;
for (int i = 1; i <= x; i = pos + 1){
pos = x / (x / i);
lasttemp = temp;
temp = getsum(pos);
ans += x / i*(temp - lasttemp);
}
printf("%lld\n", ans);
}
}
}
return 0;
}