组合数学专题
专题简介
本专题包含了一些组合数学中常见的套路和方法,如拉格朗日插值,动态规划,容斥原理,狄利克雷卷积,线性筛,杜教筛 等等.
目录
- 2018 四川省赛GRISAIA (数论分块)
- HDU 6428 Calculate (狄利克雷卷积,线性筛)
- BZOJ4559 成绩比较 (动态规划,拉格朗日插值)
- BZOJ 2633 已经没有什么好害怕的了 (容斥森林,动态规划)
- BZOJ 3930 选数 (容斥原理,动态规划)
- 徐州网络赛 D Easy Math (积性函数,线性筛)
- NWERC2015 Debugging (动态规划,数论分块)
四川省赛 GRISAIA
问题描述
求∑i=1n∑j=1in%(i∗j)\sum_{i=1}^{n}\sum_{j=1}^{i}{n \% (i * j)}∑i=1n∑j=1in%(i∗j),其中1≤n≤10111 \le n \le 10^{11}1≤n≤1011
引入的一些性质
⌈nxy⌉=⌈⌈nx⌉y⌉\lceil \frac{n}{xy} \rceil = \lceil \frac{\lceil \frac{n}{x} \rceil}{y} \rceil⌈xyn⌉=⌈y⌈xn⌉⌉
⌊nxy⌋=⌊⌊nx⌋y⌋\lfloor \frac{n}{xy} \rfloor = \lfloor \frac{\lfloor \frac{n}{x} \rfloor}{y} \rfloor⌊xyn⌋=⌊y⌊xn⌋⌋
解法
先对原问题进行变形:
∑i=1n∑j=1in%(i∗j)\sum_{i=1}^{n}\sum_{j=1}^{i}{n \% (i * j)}∑i=1n∑j=1in%(i∗j)
=∑i=1n∑j=1i(n−⌊(nij)⌋∗ij)= \sum_{i=1}^{n}\sum_{j=1}^{i}{(n - \lfloor (\frac{n}{ij}) \rfloor * ij)}=∑i=1n∑j=1i(n−⌊(ijn)⌋∗ij)
只需要求:
∑i=1n∑j=1i⌊(nij)⌋∗ij\sum_{i=1}^{n}\sum_{j=1}^{i}{\lfloor (\frac{n}{ij}) \rfloor * ij}∑i=1n∑j=1i⌊(ijn)⌋∗ij
=12∑i=1n∑j=1n(⌊(nij)⌋∗ij)+12∑i=1n⌊ni2⌋∗i2= \frac{1}{2}\sum_{i=1}^{n}\sum_{j=1}^{n}{(\lfloor (\frac{n}{ij}) \rfloor * ij}) + \frac{1}{2}\sum_{i=1}^{n}{\lfloor \frac{n}{i^2} \rfloor * i^2}=21∑i=1n∑j=1n(⌊(ijn)⌋∗ij)+21∑i=1n⌊i2n⌋∗i2
只需要求:
∑i=1n∑j=1n(⌊(nij)⌋∗ij)\sum_{i=1}^{n}\sum_{j=1}^{n}{(\lfloor (\frac{n}{ij}) \rfloor * ij})∑i=1n∑j=1n(⌊(ijn)⌋∗ij)
这里我们就要使用引入的性质了:
=∑i=1n∑j=1n(i∗⌊⌊ni⌋j⌋∗j)= \sum_{i = 1}^{n}\sum_{j = 1}^{n}(i * \lfloor \frac{\lfloor \frac{n}{i} \rfloor}{j} \rfloor * j)=∑i=1n∑j=1n(i∗⌊j⌊in⌋⌋∗j)
=∑i=1ni∑j=1n(⌊⌊ni⌋j⌋∗j)= \sum_{i = 1}^{n}{i}\sum_{j = 1}^{n}( \lfloor \frac{\lfloor \frac{n}{i} \rfloor}{j} \rfloor * j)=∑i=1ni∑j=1n(⌊j⌊in⌋⌋∗j)
记录
f(n)=∑i=1n⌊ni⌋∗if(n) = \sum_{i = 1}^{n}{\lfloor \frac{n}{i} \rfloor} * if(n)=∑i=1n⌊in⌋∗i
显然
∑i=1n∑j=1n(i∗⌊⌊ni⌋j⌋∗j)=∑i=1ni∗f(⌊ni⌋)\sum_{i = 1}^{n}\sum_{j = 1}^{n}(i * \lfloor \frac{\lfloor \frac{n}{i} \rfloor}{j} \rfloor * j) = \sum_{i = 1}^{n}{i*f(\lfloor \frac{n}{i} \rfloor)}∑i=1n∑j=1n(i∗⌊j⌊in⌋⌋∗j)=∑i=1ni∗f(⌊in⌋)
显然f(n)f(n)f(n)的计算可以数论分块,然后上面部分的计算也可以分块.
HDU6428 [Calculate]
知识清单
- 欧拉函数
- 莫比乌斯函数
- 莫比乌斯反演
- 狄利克雷卷积
- 线性筛
题解
要求计算∑i=1A∑j=1b∑k=1C(ϕ(gcd(i,j2,k3)))\sum_{i = 1}^{A}\sum_{j = 1}^{b}\sum_{k = 1}^{C}(\phi(gcd(i,j^2,k^3)))∑i=1A∑j=1b∑k=1C(ϕ(gcd(i,j2,k3)))
我们先对式子进行变换,比较套路一步是改为枚举gcdgcdgcd,然后统计gcdgcdgcd出现的次数,即:
=∑d=1ϕ(d)(∑i=1A∑j=1b∑k=1C[gcd(i,j2,k3)=d])= \sum_{d = 1}{\phi{(d)}}{(\sum_{i = 1}^{A}\sum_{j = 1}^{b}\sum_{k = 1}^{C}[gcd(i,j^2,k^3)=d])}=∑d=1ϕ(d)(∑i=1A∑j=1b∑k=1C[gcd(i,j2,k3)=d])
对上述式子中的∑i=1A∑j=1b∑k=1C[gcd(i,j2,k3)=d]\sum_{i = 1}^{A}\sum_{j = 1}^{b}\sum_{k = 1}^{C}[gcd(i,j^2,k^3)=d]∑i=1A∑j=1b∑k=1C[gcd(i,j2,k3)=d]再进行莫比乌斯反演:
设g(d)=∑i=1A∑j=1b∑k=1C[gcd(i,j2,k3)=d]g(d) = \sum_{i = 1}^{A}\sum_{j = 1}^{b}\sum_{k = 1}^{C}[gcd(i,j^2,k^3)=d]g(d)=∑i=1A∑j=1b∑k=1C[gcd(i,j2,k3)=d]
并设G[n]=∑n∣dg(d)G[n] = \sum_{n|d}{g(d)}G[n]=∑n∣dg(d)
那么显然有g(n)=∑n∣dμ(dn)G(d)g(n) = \sum_{n|d}\mu(\frac{d}{n})G(d)g(n)=∑n∣dμ(nd)G(d)
那么要求的式子可以写成
=∑d=1ϕ(d)g(d)=∑d=1ϕ(d)∑d∣tμ(td)G(t)= \sum_{d = 1}{\phi{(d)}} g(d) = \sum_{d = 1}{\phi{(d)}}\sum_{d|t}\mu(\frac{t}{d})G(t)=∑d=1ϕ(d)g(d)=∑d=1ϕ(d)∑d∣tμ(dt)G(t)
由于ϕ(x)\phi(x)ϕ(x) 与μ(x)\mu(x)μ(x)都是积性函数,故将G[t]G[t]G[t]和ϕ(d)\phi(d)ϕ(d)的位置做交换,使之形成狄利克雷卷积.
=∑t=1G(t)∑d∣tμ(td)ϕ(d)= \sum_{t=1}G(t)\sum_{d|t}\mu(\frac{t}{d})\phi(d)=∑t=1G(t)∑d∣tμ(dt)ϕ(d)
在这里,记F(x)=∑d∣tμ(td)ϕ(d)F(x) = \sum_{d|t}\mu(\frac{t}{d})\phi(d)F(x)=∑d∣tμ(dt)ϕ(d)
F(x)F(x)F(x)是积性函数的狄利克雷卷积,因此F(x)F(x)F(x)也是积性函数,可以用线性筛法求得.
线性筛所能用到的信息:
F(p)=p−2F(p) = p-2F(p)=p−2
F(px)=F(px−1)∗pF(p^x) = F(p^{x-1})*pF(px)=F(px−1)∗p
F(p2)=p2−2∗p+1F(p^2) = p^2-2*p+1F(p2)=p2−2∗p+1
有关G(n)G(n)G(n)的计算:
若n=∏piain = \prod{p_i^{a_i}}n=∏piai
则定义
g2(n)=∏pi⌈ai2⌉g_2(n) = \prod{p_i^{\lceil \frac{a_i}{2} \rceil}}g2(n)=∏pi⌈2ai⌉
g3(n)=∏pi⌈ai3⌉g_3(n) = \prod{p_i^{\lceil \frac{a_i}{3} \rceil}}g3(n)=∏pi⌈3ai⌉
那么G(n)=[An][Bg2(n)][Cg3(n)]G(n) = [\frac{A}{n}][\frac{B}{g_2(n)}][\frac{C}{g_3(n)}]G(n)=[nA][g2(n)B][g3(n)C]
这样的话式子=∑t=1G(t)F(t)= \sum_{t=1}G(t)F(t)=∑t=1G(t)F(t)中的G(t)G(t)G(t)和F(t)F(t)F(t)均可在O(n)O(n)O(n)线性时间复杂度内预处理出来.
另外:这道题卡常数,真的恶心.
代码
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const ll MOD = 1<<30;
const int maxn = 10000000;
int zhi[maxn+10],prime[maxn+10],pcnt,F[maxn+10];
int low[maxn+10],lowm[maxn+10],g2[maxn+10],g3[maxn+10];
void sieve(){zhi[1] = 1;low[1] = F[1] = g3[1] = g2[1] = 1;lowm[1] = 0;for(int i = 2;i <= maxn;++i){if(!zhi[i]) {F[i] = i-2,prime[pcnt++] = i;low[i] = i;lowm[i] = 1;g3[i] = g2[i] = i;}for(int j = 0;j < pcnt && prime[j]*i <= maxn;++j){zhi[i*prime[j]] = 1;if(i % prime[j] == 0){low[i*prime[j]] = low[i] * prime[j];lowm[i*prime[j]] = lowm[i]+1;if(i == low[i]){if(i == prime[j]) F[i*prime[j]] = prime[j]*prime[j]-2*prime[j]+1;else F[i*prime[j]] = F[i] * prime[j];}else{F[i*prime[j]] = F[i/low[i]]*F[low[i]*prime[j]];}g2[i*prime[j]] = g2[i];g3[i*prime[j]] = g3[i];if(lowm[i*prime[j]] % 2 == 1) g2[i*prime[j]] *= prime[j];if(lowm[i*prime[j]] % 3 == 1) g3[i*prime[j]] *= prime[j];break;}else{F[i*prime[j]] = F[i] * F[prime[j]];low[i*prime[j]] = prime[j];lowm[i*prime[j]] = 1;g2[i*prime[j]] = g2[i] * prime[j];g3[i*prime[j]] = g3[i] * prime[j];}}}
}int T ,A,B,C;
int main(){sieve();cin>>T;while(T--){scanf("%d%d%d",&A,&B,&C);ll ans = 0;for(int n = 1;n <= max(A,max(B,C));++n){ans = (ans + (A/n)*(B/g2[n])%MOD*(C/g3[n])%MOD*F[n]%MOD) % MOD;}cout<<ans<<endl;}return 0;
}
BZOJ4559 [成绩比较]
知识清单
- 动态规划
- 拉格朗日插值
题解
分两部分来解决:
1.先不考虑具体分数,只考虑满足相对排名计算得到方案数.
定义dp[i][j]dp[i][j]dp[i][j]表示仅考虑前iii节课,B神恰好碾压jjj个人.
转移方法:
显然dp[i][j]dp[i][j]dp[i][j]由dp[i][j+k]dp[i][j+k]dp[i][j+k]转移过来.
具体转移方法:
考虑第iii门课,从j+kj+kj+k中选出kkk个,他们的分数大于B神,剩下jjj个放在B神的下面,表示继续被碾压.方案总数Ck+jkC_{k+j}^{k}Ck+jk.
剩下的还有N−1−(j+k)N-1-(j+k)N−1−(j+k)名同学,这些同学中取出Rank[i]−kRank[i]-kRank[i]−k名同学放在B神上面,填满Rank[i]Rank[i]Rank[i].方案总数CN−1−j−kRank[i]−kC_{N-1-j-k}^{Rank[i]-k}CN−1−j−kRank[i]−k.
因此转移方程:
dp[i][j]=∑k=0k+j≤N−1dp[i][j+k]Ck+jkCN−1−j−kRank[i]−kdp[i][j] = \sum_{k=0}^{k+j≤N-1}{dp[i][j+k]C_{k+j}^{k}}C_{N-1-j-k}^{Rank[i]-k}dp[i][j]=∑k=0k+j≤N−1dp[i][j+k]Ck+jkCN−1−j−kRank[i]−k
dp初始化:
dp[0][N−1]=1dp[0][N-1] = 1dp[0][N−1]=1
该部分具体实现代码:
// dp[i][j]表示仅考虑前i门课程,其中有j名同学被B神碾压,的相对排名的方案数.
dp[0][N-1] = 1;
for(int i = 1;i <= M;++i){for(int j = N-1;j >= 0;--j){for(int k = 0;k+j<=N-1;++k){ll res = C[j+k][k] * C[N-1-(j+k)][R[i]-1-k] % MOD;res = res * dp[i-1][j+k] % MOD;dp[i][j] = (dp[i][j] + res) % MOD;}}
}
2.求出满足题目给出的排名条件的分数具体分配方案数.
对于第iii门课,设B神的排名为Rank[i]Rank[i]Rank[i],则该门课产生的方案数就是枚举该门课B神的分数得到的方案数之和:
LRank[i][Ux]=∑t=1UxtN−Rank[i](Ux−t)Rank[i]−1L_{Rank[i]}[U_x] = \sum_{t=1}^{Ux}{t^{N-Rank[i]}(U_x-t)^{Rank[i]-1}}LRank[i][Ux]=∑t=1UxtN−Rank[i](Ux−t)Rank[i]−1
这个式子是关于UxU_xUx的NNN次多项式,而UxUxUx最大则可能达到1e91e91e9,显然不可能直接来求,我们考虑拉格朗日插值.
LagrangeLagrangeLagrange插值简介
对于一个nnn次的多项式,只需要n+1n+1n+1个点即可唯一确定,而用n+1n+1n+1个点恢复这个nnn次多项式的方法就是LagrangeLagrangeLagrange插值法.
设(xi,yi)(x_i,y_i)(xi,yi)为我们已经获得的N+1N+1N+1个点.
定义fi[x]=∏i!=jx−xjxi−xjf_i[x] = \prod_{i!=j}{\frac{x-x_j}{x_i-x_j}}fi[x]=∏i!=jxi−xjx−xj
那么L[x]=∑i=1N+1yifi[x]L[x] = \sum_{i=1}^{N+1}{y_if_i[x]}L[x]=∑i=1N+1yifi[x]
如果这道题用LagrangeLagrangeLagrange插值求得了L[x]L[x]L[x],那么第iii门课的答案即是LRank[i][Ux]L_{Rank[i]}[U_x]LRank[i][Ux].
//拉格朗日实现具体代码
ll Lagrange(int id){//拉格朗日插值for(int xi = 1;xi <= N+1;++xi){x[xi] = xi;y[xi] = 0;for(int i = 1;i <= xi;++i){y[xi] = (y[xi] + (mod_pow(i,N-R[id])*mod_pow(xi-i,R[id]-1)%MOD)) % MOD;}}ll ans = 0;for(int xi = 1;xi <= N+1;++xi){ll res = y[xi];for(int xj = 1;xj <= N+1;++xj){if(xj == xi) continue;res = res * (U[id] - xj + MOD) % MOD;res = res * div(xi - xj + MOD) % MOD;}ans = (ans + res) % MOD;}return ans;
}
综合起来,这道题的答案就是ans=dp[M][K]∗∏i=1MLRank[i][Ux]ans = dp[M][K]*\prod_{i=1}^{M}{L_{Rank[i]}[U_x]}ans=dp[M][K]∗∏i=1MLRank[i][Ux]
###实现代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <stack>using namespace std;#define pr(x) cout<<#x<<":"<<x<<endl
typedef long long ll;
int N,M,K;
const ll MOD = 1e9+7;
ll mod_pow(ll x,ll n){if(n == 0) return 1;ll res = 1;while(n){if(n&1) res = res * x %MOD;x = x * x % MOD;n >>= 1;}return res;
}
const int maxn = 107;
ll R[maxn],U[maxn],dp[maxn][maxn];
ll C[maxn][maxn];
ll div(ll x){return mod_pow(x,MOD-2);
}
void initC(){C[0][0] = 1;for(int i = 1;i < maxn;++i){C[i][0] = 1;for(int j = 1;j <= i;++j){C[i][j] = (C[i-1][j] + C[i-1][j-1])%MOD;}}
}
ll x[maxn],y[maxn];
ll Lagrange(int id){//拉格朗日插值for(int xi = 1;xi <= N+1;++xi){x[xi] = xi;y[xi] = 0;for(int i = 1;i <= xi;++i){y[xi] = (y[xi] + (mod_pow(i,N-R[id])*mod_pow(xi-i,R[id]-1)%MOD)) % MOD;}}ll ans = 0;for(int xi = 1;xi <= N+1;++xi){ll res = y[xi];for(int xj = 1;xj <= N+1;++xj){if(xj == xi) continue;res = res * (U[id] - xj + MOD) % MOD;res = res * div(xi - xj + MOD) % MOD;}ans = (ans + res) % MOD;}return ans;
}
int main(){initC();ios::sync_with_stdio(false);cin>>N>>M>>K;for(int i = 1;i <= M;++i) cin>>U[i];for(int i = 1;i <= M;++i) cin>>R[i];// dp[i][j]表示仅考虑前i门课程,其中有j名同学被B神碾压,的相对排名的方案数.dp[0][N-1] = 1;for(int i = 1;i <= M;++i){for(int j = N-1;j >= 0;--j){for(int k = 0;k+j<=N-1;++k){ll res = C[j+k][k] * C[N-1-(j+k)][R[i]-1-k] % MOD;res = res * dp[i-1][j+k] % MOD;dp[i][j] = (dp[i][j] + res) % MOD;}}}ll ans = dp[M][K];for(int i = 1;i <= M;++i){ans = ans * Lagrange(i) % MOD;}cout<<ans<<endl;return 0;
}
BZOJ2633 [已经没有什么好害怕的了]
知识清单
- 容斥原理
- 动态规划
题意
给出nnn个数 aia_iai和nnn个数bib_ibi,将aia_iai和bib_ibi进行匹配,使得匹配中ai>bia_i>b_iai>bi的对数比ai<bia_i<b_iai<bi的匹配数大kkk组.
题解
首先将a,ba,ba,b数组排序,然后对于每个iii求出满足bj<ai(1≤j≤n)b_j < a_i (1≤j≤n)bj<ai(1≤j≤n)的jjj的个数,并记为cnt[i]cnt[i]cnt[i].
定义f[i][j]f[i][j]f[i][j]表示仅考虑前iii个aaa中,满足有jjj对匹配使得au>bva_u > b_vau>bv成立的方案数.(显然1≤j≤i1≤j≤i1≤j≤i) (注意这个定义中不要求剩下的aaa和bbb进行匹配.)
那么我们可以得到转移方程:
f[i][j]=f[i−1][j]+f[i−1][j−1]∗(cnt[i]−(j−1))f[i][j] = f[i-1][j] + f[i-1][j-1]*(cnt[i]-(j-1))f[i][j]=f[i−1][j]+f[i−1][j−1]∗(cnt[i]−(j−1))
看起来似乎f[n][j]∗(n−j)!f[n][j]*(n-j)!f[n][j]∗(n−j)!就是我们要找的答案?
但显然并不是,因为(n−j)!(n-j)!(n−j)!表示的剩下未匹配的元素进行随机搭配的时候,有时候也会搭配出满足au>bva_u > b_vau>bv的匹配,像这种就是不合法的,因此我们还要进行去重操作.
设g[i]g[i]g[i]表示在nnn个匹配中,恰好有iii个匹配满足au>bva_u > b_vau>bv的方案数.显然这回g[(n+k)/2]g[(n+k)/2]g[(n+k)/2]就是我们要的答案.
容易想到转移方程:
g[i]=f[n][i]∗(n−i)!−g[i+1]∗Ci+1i−g[i+2]∗CI+2i−...−g[n]∗Cnig[i]=f[n][i]*(n-i)!-g[i+1]*C^{i}_{i+1}-g[i+2]*C^{i}_{I+2}-...-g[n]*C^{i}_{n}g[i]=f[n][i]∗(n−i)!−g[i+1]∗Ci+1i−g[i+2]∗CI+2i−...−g[n]∗Cni
解释一下为什么要乘以CjiC^{i}_{j}Cji这个系数:因为f[n][i]∗(n−i)!f[n][i]*(n-i)!f[n][i]∗(n−i)!产生的满足有jjj个au>bva_u > b_vau>bv的匹配数的方案数可以由固定iii个匹配数来得到(剩下的j−ij-ij−i个匹配由(j−i)!(j-i)!(j−i)!部分负责).而固定iii个匹配数的方案数刚好就是CjiC^{i}_{j}Cji
得到这个式子倒着转移就可以了.
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <stack>using namespace std;
const ll MOD = 1e9+9;
int n,k;
const int maxn = 2007;
ll f[maxn][maxn],g[maxn];
ll a[maxn],b[maxn];
ll fac[maxn];
ll C[maxn][maxn];
ll cnt[maxn];
int main(){C[0][0] = fac[0] = 1;for(int i = 1;i <= 2000;++i) {fac[i] = fac[i-1] * i % MOD;C[i][0] = 1;}for(int i = 1;i <= 2000;++i) {for(int j = 1;j <= i;++j){C[i][j] = (C[i-1][j] + C[i-1][j-1]) % MOD;}}cin>>n>>k;if((n+k)%2 != 0) return puts("0");for(int i = 1;i <= n;++i) scanf("%lld",&a[i]);for(int i = 1;i <= n;++i) scanf("%lld",&b[i]);sort(a+1,a+1+n);sort(b+1,b+1+n);for(int i = 1;i <= n;++i){cnt[i] = lower_bound(b+1,b+1+n,a[i])-b-1;}f[0][0] = 1;for(int i = 1;i <= n;++i){f[i][0] = 1;for(int j = 1;j <= i;++j){f[i][j] = f[i-1][j] + (f[i-1][j-1]*max(cnt[i]-j+1,0LL)%MOD);f[i][j] %= MOD;}}for(int i = n;i >= (n+k)/2;--i){g[i] = f[n][i]*fac[n-i]%MOD;for(int j = i+1;j <= n;++j){g[i] = (g[i] + MOD - (C[j][i] * g[j] % MOD)) % MOD;}}cout<<g[(n+k)/2]<<endl;return 0;
}
BZOJ3930 [选数]
知识清单
- 容斥原理 (或莫比乌斯反演)
题解
题目给的一个很关键的信息就是H−L≤1e5H-L ≤ 1e5H−L≤1e5.这意味着如果在区间[L,H][L,H][L,H]中选出NNN个不完全相同的整数,那么他们的GCD≤H−L≤1e5GCD≤H-L≤1e5GCD≤H−L≤1e5
证明:GCD(x,y)=GCD(x−y,y)<=x−yGCD(x,y) = GCD(x-y,y) <= x-yGCD(x,y)=GCD(x−y,y)<=x−y
如果将[L,H][L,H][L,H]中的各个元素除以KKK,就相当于在(L−1K,HK](\frac{L-1}{K},\frac{H}{K}](KL−1,KH]中找NNN个不完全相同的数使得其GCD=1GCD = 1GCD=1
设F[x]F[x]F[x]表示满足在(L−1K,HK](\frac{L-1}{K},\frac{H}{K}](KL−1,KH]中找NNN个不完全相同的元素,使得其GCD=1GCD=1GCD=1的方案数.
显然F[x]=([HKx]−[L−1Kx])N−([HKx]−[L−1Kx])−F[2∗x]−F[3∗x]−....F[x] = ([\frac{H}{Kx}]-[\frac{L-1}{Kx}])^N - ([\frac{H}{Kx}]-[\frac{L-1}{Kx}]) - F[2*x] - F[3*x] -....F[x]=([KxH]−[KxL−1])N−([KxH]−[KxL−1])−F[2∗x]−F[3∗x]−....
遇见这种递推式子直接倒着进行转移就可以了.
注意,如果 KKK 存在于区间[L,H][L,H][L,H]之间,那么ans++ans++ans++,因为这种属于NNN个数字全部相等的情况,我们的算法是没有考虑进去的.
另外,
这题实际上可以使用莫比乌斯反演,而且莫比乌斯反演形式特别直接,但是复杂度不对.
重新定义F[x]F[x]F[x]为在[L,H][L,H][L,H]中找NNN个数使得其GCD=XGCD=XGCD=X的方案数
我们可以得到
∑n∣dF[d]=([Hn]−[L−1n])N\sum_{n|d}{F[d]} = ([\frac{H}{n}]-[\frac{L-1}{n}])^N∑n∣dF[d]=([nH]−[nL−1])N
反演得到,
F[n]=∑n∣dμ(dn)([Hd]−[L−1d])NF[n] = \sum_{n|d}{\mu(\frac{d}{n})([\frac{H}{d}]-[\frac{L-1}{d}])^N}F[n]=∑n∣dμ(nd)([dH]−[dL−1])N
我看到其他的博客有讲到分块的方法,但感觉复杂度有点玄学.
const ll MOD = 1e9+7;
ll N,K,L,R;
ll mod_pow(ll x,ll n){ll res = 1;while(n){if(n & 1) res = res * x % MOD;x = x * x % MOD;n >>= 1;}return res;
}
ll F[200007];
const int maxn = 200000;
int main(){cin>>N>>K>>L>>R;ll ans = K >= L && K <= R;R /= K;L --;L /= K;ll len = R - L;for(ll d = len;d >= 1;--d){ll res = R/d - L/d;F[d] = (mod_pow(res,N)-res+MOD) % MOD;for(ll k = 2*d;k <= len;k += d)F[d] = (F[d] - F[k] + MOD) % MOD;}cout<<ans+F[1]<<endl;return 0;
}
徐州网络赛 Easy Math
前置技能
- 容斥原理
- 线性筛
- 杜教筛
题解
∑i=1mμ(in)\sum_{i=1}^{m}\mu(in)∑i=1mμ(in)
当nnn存在平方因子的时候,直接输出000作为答案,这是很显然的.
否则,n=p1p2...pkn = p_1p_2...p_kn=p1p2...pk,不含平方因子.
考虑区间[1,m][1,m][1,m]中的数字iii,如果iii不含有因子p1p_1p1,则可以使用积性函数的性质.
即:μ(i∗n)=μ(i∗np1∗p1)=−μ(i∗np1)\mu(i*n) = \mu(i*\frac{n}{p_1}*p_1) = -\mu(i*\frac{n}{p_1})μ(i∗n)=μ(i∗p1n∗p1)=−μ(i∗p1n)
而区间[1,m][1,m][1,m]中的数并不全都与p1p_1p1互质,这就会造成μ(i∗n)=0\mu(i*n) = 0μ(i∗n)=0 而 μ(i∗np1)≠0\mu(i*\frac{n}{p_1}) \ne 0μ(i∗p1n)̸=0的情况,这就要求我们用容斥的思想来去掉这部分.
如果iii中恰好有一个因子p1p_1p1,则μ(i∗np1)=μ(ip1∗n)\mu(i*\frac{n}{p_1}) = \mu(\frac{i}{p_1}*n)μ(i∗p1n)=μ(p1i∗n)
因此要减去∑i=1⌊np1⌋μ(in)\sum_{i=1}^{\lfloor \frac{n}{p_1} \rfloor} \mu(in)∑i=1⌊p1n⌋μ(in)
而如果iii中有多于111个的pip_ipi因子,那么这部分的莫比乌斯函数值无论是在那部分中都是000,因此不会对最终答案造成影响.
这样相当于降低了问题的规模.
我们可以列出递推式子,然后递推解决这个问题.
∑i=1nμ(in)=−∑i=1nμ(i∗np1)+∑i=1⌊np1⌋μ(in)\sum_{i=1}^{n}{\mu(in)} = -\sum_{i=1}^{n}{\mu(i*\frac{n}{p_1})} + \sum_{i=1}^{\lfloor \frac{n}{p_1} \rfloor} \mu(in)∑i=1nμ(in)=−∑i=1nμ(i∗p1n)+∑i=1⌊p1n⌋μ(in)
递推终止的条件是
- n==1n == 1n==1 时候,使用杜教筛得到莫比乌斯函数的前缀和
- ∑\sum∑的上限为000时候,直接返回000.
typedef long long ll;
const int N = 10000000;;
int mu[N+10],prime[N+10],pcnt,zhi[N+10];
void sieve(){pcnt = 0;mu[1] = zhi[1] = 1;for(int i = 2;i <= N;++i){if(!zhi[i]) mu[i] = -1,prime[pcnt++] = i;for(int j = 0;j < pcnt && prime[j]*i <= N;++j){zhi[i*prime[j]] = 1;if(i % prime[j] == 0){mu[i*prime[j]] = 0;break;}else{mu[i*prime[j]] = -mu[i];}}}for(int i = 1;i <= N;++i) mu[i] += mu[i-1];
}
map<ll,int> rec,vis;
int Mu(int x){if(x <= N) return mu[x];if(vis[x]) return rec[x];int res = 1,now = x,nxt;while(now >= 2){nxt = x/(x/now+1);res -= (now - nxt) * Mu(x/now);now = nxt;}vis[x] = 1;return rec[x] = res;
}
ll m,n;
ll np[20];int npcnt;
int Ans(ll u,ll d){if(d == 1) return Mu(u);if(u == 0) return 0;for(int i = 0;i < npcnt;++i){if(d % np[i] == 0){return -Ans(u,d/np[i]) + Ans(u/np[i],d);}}
}
bool divide(){ll x = n;for(ll i = 2;i * i <= n;++i){int cc = 0;if(x % i == 0) np[npcnt++] = i;while(x % i == 0) x /= i,cc++;if(cc >= 2) return false;}if(x > 1) np[npcnt++] = x;return true;
}
int main(){sieve();cin>>m>>n;if(!divide()) return puts("0");cout<<Ans(m,n)<<endl;return 0;
}
NWERC2015 Debugging
题目描述
有一份包含一个Bug的n,(1≤n≤106)n,(1 \le n \le 10^6 )n,(1≤n≤106)行代码,运行一次到崩溃的时间为r,1≤r≤109r,1 \le r \le 10^9r,1≤r≤109.
现在你可以在任意一行花费时间p,1≤p≤109p,1\le p \le 10^9p,1≤p≤109设置一个printfprintfprintf语句来判断程序是否运行到这里.
请问在最坏情况下,最少需要多少时间可以定位到bug所在行.
题解
因为我们调试的时候往往会运行很多次程序,而每次运行完之后,我们都能定位bug在哪一块代码行中.
因此我们动态规划的思想得到递推公式.
记dp[n]dp[n]dp[n]表示调试nnn行代码所需要花的时间.
dp[n]=min1≤k≤n(dp[nk+1]+k∗p+r)dp[n] = min_{1 \le k \le n}(dp[\frac{n}{k+1}]+k*p + r)dp[n]=min1≤k≤n(dp[k+1n]+k∗p+r)
直接递推的时间复杂度是O(n2)O(n^2)O(n2)的,而如果我们发现对于[nk+1][\frac{n}{k+1}][k+1n]相等的kkk,我们只取kkk的最小值就可以了.
所以通过底数分块,我们可以求得转移的时间复杂度为n\sqrt{n}n,而真正有效的状态数也只有n\sqrt{n}n,所以时间复杂度上限不会超过O(n)O(n)O(n).