经过本人的研究,发现似乎并没有数学天赋 -o-

中表示 为 的约束
快速寻找约束的方法:
直接遍历约束,

切比雪夫定理 : 对于一个大于 1 的 , 在 之间一定存在至少一个质数

莫比乌斯函数

定义域

, 为 素数或者为奇数个素数之积
, 为偶数个素数之积
, 存在平方因子

性质1

, 当 为互质的时候
互质:

莫比乌斯反演

三种应用

计算 的个数
计算 的个数
计算 的幂

对于应用 1, 2 :(这里的 为 , )

一维计算 的个数: 与 的

例题:最大公约数图 注意边界!注意边界!注意边界!

vi pr;
vi b(N + 10);
vi f(N + 10, 1);
vi mu(N + 10);
vi dp(N + 2);
 
void prime(){
	rep(i, 1, N) b[i] = i;
	
	f[0] = 0, f[1] = 0;
	mu[1] = 1;
	rep(i, 2, N){
		if(f[i] == 1){
			pr.push_back(i);
			mu[i] = -1;	//素数
		}
		rep(j, 0, pr.size() - 1){
			if(pr[j] * i > N) break;			
			b[pr[j] * i] = i;
			f[pr[j] * i] = 0;
			if(i % pr[j] == 0){
				mu[pr[j] * i] = 0; //含有平方因子
				break;	
			}else{
				mu[pr[j] * i] = -mu[i]; //奇偶翻转
			}
		}
	}
	return;
}
 
void solve(){
	int l, r, n; cin >> l >> r >> n;
 
	if(f[n] == 1){
		cout << r - l + 1 << endl;
		return;
	}
 
	int res = 0;
	int k = lower_bound(pr.begin(), pr.end(), n) - pr.begin() - 1;
	
	if(l <= pr[k]){
	res = (min(pr[k], r) - l + 1) * 2; 
	
	int x = sqrt(n);
	rep(i, 1, x){
		if(n % i) continue;
		int j = n / i;
		res -= mu[i] * ((min(pr[k], r) / i) - (l - 1) / i);
		if(j != i) res -= mu[j] * ((min(pr[k], r) / j) - (l - 1) / j);
	}
	
	}
	per(i, n, pr[k] + 1){
		dp[i] = __gcd(i, n);
		rep(j, i + 1, n - 1){
			dp[i] = min(dp[i], dp[j] + __gcd(i, j));
		}
		if(i <= r && i >= l) res += dp[i];
	}
	rep(i, pr[k] + 1, n) dp[i] = 0; 
	cout << res << endl;
	return;
}
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int T = 1;
	cin >> T;
	prime();
	while(T --) solve();
	return 0;
}

三种反演形式

约数反演 - 是 的倍数

若存在

倍数反演 - 是 的倍数

若存在

指数反演 - 略

倍数反演例题

Increment All Divisors

题意
给定一个长度为 的整数序列

你可以对 进行以下操作:

  • 选择一个 的整数 。 对于每个作为 的正因数(也就是约数) ,

请判断是否可以通过任意次这样的操作使整个序列相等,如果可以请输出最小的操作数,如果不可以输出 -1


题解

  • 错误思路:
    直接遍历每个数的倍数,如果它的倍数们有比它小的,那么就输出 -1 , 如果都没有,说明一定可以达到,操作数等于 最大数 - 最小数

但是由于最后达到的数不一定是一开始序列的最大数,所以这个思路错误了

  • 正确思路:
    的次数为它的倍数被选择的次数加它自己被选择的次数

    记 为我们选择 的次数, 记 为最后到达的数(也就是序列元素相等时的每个元素的数)

保证 一定大于等于 0 , 维护最大下届和最小上界

void prime(){
    rep(i, 1, N - 10) b[i] = i;
 
    f[0] = 0; f[1] = 0;
    mu[1] = 1;
 
    rep(i, 2, N - 10){
        if(f[i] == 1){
            pr.push_back(i);
            mu[i] = -1;
        }
        rep(j, 0, pr.size() - 1){
            if(pr[j] * i > N - 10) break;
            b[pr[j] * i] = i;
            f[pr[j] * i] = 0;
            if(i % pr[j] == 0){
                mu[pr[j] * i] = 0;
                break;
            }else{
                mu[pr[j] * i] = -mu[i];
            }
        }
    }
    return;
}
 
void solve(){
    prime();
    int n; cin >> n;
    
    vi a(n + 1);
    rep(i, 1, n){
        cin >> a[i];
    }
    
    int l = 0, r = INF; //维护最大下届和最小上界, 计算出来的最终 M 的值范围,我们答案需要输出的是操作次数
    rep(i, 1, n){
        int p = 0, q = 0;
        rep(j, 1, n){
            if(i * j > n) break;
            p += mu[j];
            q += mu[j] * a[i * j];
        }
        if(p > 0){
            int x = q / p;
            if(q % p) x ++;
            l = max(l, x);
        }else if(p < 0){
            r = min(r, q / p);
        }else if(q > 0){ // x_i = pM - q, p = 0, x_i >= 0 , -> q <= 0
            cout << -1 << endl;
            return; 
        }
    }
 
    if(l - a[1] < 0 || l > r){
        cout << -1 << endl;
    }else{
        cout << l - a[1] << endl;
    }
    return;
}