组合数学专题
专题简介
本专题包含了一些组合数学中常见的套路和方法,如拉格朗日插值,动态规划,容斥原理,狄利克雷卷积,线性筛,杜教筛 等等.
目录
- 2018 四川省赛GRISAIA (数论分块)
- HDU 6428 Calculate (狄利克雷卷积,线性筛)
- BZOJ4559 成绩比较 (动态规划,拉格朗日插值)
- BZOJ 2633 已经没有什么好害怕的了 (容斥森林,动态规划)
- BZOJ 3930 选数 (容斥原理,动态规划)
- 徐州网络赛 D Easy Math (积性函数,线性筛)
- NWERC2015 Debugging (动态规划,数论分块)
四川省赛 GRISAIA
问题描述
求 ∑ i = 1 n ∑ j = 1 i n % ( i ∗ j ) \sum_{i=1}^{n}\sum_{j=1}^{i}{n \% (i * j)} ∑i=1n∑j=1in%(i∗j),其中 1 ≤ n ≤ 1 0 11 1 \le n \le 10^{11} 1≤n≤1011
引入的一些性质
⌈
n
x
y
⌉
=
⌈
⌈
n
x
⌉
y
⌉
\lceil \frac{n}{xy} \rceil = \lceil \frac{\lceil \frac{n}{x} \rceil}{y} \rceil
⌈xyn⌉=⌈y⌈xn⌉⌉
⌊
n
x
y
⌋
=
⌊
⌊
n
x
⌋
y
⌋
\lfloor \frac{n}{xy} \rfloor = \lfloor \frac{\lfloor \frac{n}{x} \rfloor}{y} \rfloor
⌊xyn⌋=⌊y⌊xn⌋⌋
解法
先对原问题进行变形:
∑ i = 1 n ∑ j = 1 i n % ( i ∗ j ) \sum_{i=1}^{n}\sum_{j=1}^{i}{n \% (i * j)} ∑i=1n∑j=1in%(i∗j)
= ∑ i = 1 n ∑ j = 1 i ( n − ⌊ ( n i j ) ⌋ ∗ i j ) = \sum_{i=1}^{n}\sum_{j=1}^{i}{(n - \lfloor (\frac{n}{ij}) \rfloor * ij)} =∑i=1n∑j=1i(n−⌊(ijn)⌋∗ij)
只需要求:
∑ i = 1 n ∑ j = 1 i ⌊ ( n i j ) ⌋ ∗ i j \sum_{i=1}^{n}\sum_{j=1}^{i}{\lfloor (\frac{n}{ij}) \rfloor * ij} ∑i=1n∑j=1i⌊(ijn)⌋∗ij
= 1 2 ∑ i = 1 n ∑ j = 1 n ( ⌊ ( n i j ) ⌋ ∗ i j ) + 1 2 ∑ i = 1 n ⌊ n i 2 ⌋ ∗ i 2 = \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 = 1 n ∑ j = 1 n ( ⌊ ( n i j ) ⌋ ∗ i j ) \sum_{i=1}^{n}\sum_{j=1}^{n}{(\lfloor (\frac{n}{ij}) \rfloor * ij}) ∑i=1n∑j=1n(⌊(ijn)⌋∗ij)
这里我们就要使用引入的性质了:
= ∑ i = 1 n ∑ j = 1 n ( i ∗ ⌊ ⌊ n i ⌋ 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 = 1 n i ∑ j = 1 n ( ⌊ ⌊ n i ⌋ 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 = 1 n ⌊ n i ⌋ ∗ i f(n) = \sum_{i = 1}^{n}{\lfloor \frac{n}{i} \rfloor} * i f(n)=∑i=1n⌊in⌋∗i
显然
∑ i = 1 n ∑ j = 1 n ( i ∗ ⌊ ⌊ n i ⌋ j ⌋ ∗ j ) = ∑ i = 1 n i ∗ f ( ⌊ n i ⌋ ) \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
=
1
A
∑
j
=
1
b
∑
k
=
1
C
(
ϕ
(
g
c
d
(
i
,
j
2
,
k
3
)
)
)
\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)))
我们先对式子进行变换,比较套路一步是改为枚举
g
c
d
gcd
gcd,然后统计
g
c
d
gcd
gcd出现的次数,即:
= ∑ d = 1 ϕ ( d ) ( ∑ i = 1 A ∑ j = 1 b ∑ k = 1 C [ g c d ( i , j 2 , k 3 ) = 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 = 1 A ∑ j = 1 b ∑ k = 1 C [ g c d ( i , j 2 , k 3 ) = 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 = 1 A ∑ j = 1 b ∑ k = 1 C [ g c d ( i , j 2 , k 3 ) = 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 ∣ d g ( d ) G[n] = \sum_{n|d}{g(d)} G[n]=∑n∣dg(d)
那么显然有 g ( n ) = ∑ n ∣ d μ ( d n ) 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 μ ( t d ) 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 = 1 G ( t ) ∑ d ∣ t μ ( t d ) ϕ ( 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 μ ( t d ) ϕ ( 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 − 2 F(p) = p-2 F(p)=p−2
F ( p x ) = F ( p x − 1 ) ∗ p F(p^x) = F(p^{x-1})*p F(px)=F(px−1)∗p
F ( p 2 ) = p 2 − 2 ∗ p + 1 F(p^2) = p^2-2*p+1 F(p2)=p2−2∗p+1
有关 G ( n ) G(n) G(n)的计算:
若 n = ∏ p i a i n = \prod{p_i^{a_i}} n=∏piai
则定义
g 2 ( n ) = ∏ p i ⌈ a i 2 ⌉ g_2(n) = \prod{p_i^{\lceil \frac{a_i}{2} \rceil}} g2(n)=∏pi⌈2ai⌉
g 3 ( n ) = ∏ p i ⌈ a i 3 ⌉ g_3(n) = \prod{p_i^{\lceil \frac{a_i}{3} \rceil}} g3(n)=∏pi⌈3ai⌉
那么 G ( n ) = [ A n ] [ B g 2 ( n ) ] [ C g 3 ( 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 = 1 G ( 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.先不考虑具体分数,只考虑满足相对排名计算得到方案数.
定义
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示仅考虑前
i
i
i节课,B神恰好碾压
j
j
j个人.
转移方法:
显然
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]由
d
p
[
i
]
[
j
+
k
]
dp[i][j+k]
dp[i][j+k]转移过来.
具体转移方法:
考虑第
i
i
i门课,从
j
+
k
j+k
j+k中选出
k
k
k个,他们的分数大于B神,剩下
j
j
j个放在B神的下面,表示继续被碾压.方案总数
C
k
+
j
k
C_{k+j}^{k}
Ck+jk.
剩下的还有
N
−
1
−
(
j
+
k
)
N-1-(j+k)
N−1−(j+k)名同学,这些同学中取出
R
a
n
k
[
i
]
−
k
Rank[i]-k
Rank[i]−k名同学放在B神上面,填满
R
a
n
k
[
i
]
Rank[i]
Rank[i].方案总数
C
N
−
1
−
j
−
k
R
a
n
k
[
i
]
−
k
C_{N-1-j-k}^{Rank[i]-k}
CN−1−j−kRank[i]−k.
因此转移方程:
d
p
[
i
]
[
j
]
=
∑
k
=
0
k
+
j
≤
N
−
1
d
p
[
i
]
[
j
+
k
]
C
k
+
j
k
C
N
−
1
−
j
−
k
R
a
n
k
[
i
]
−
k
dp[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初始化:
d
p
[
0
]
[
N
−
1
]
=
1
dp[0][N-1] = 1
dp[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.求出满足题目给出的排名条件的分数具体分配方案数.
对于第
i
i
i门课,设B神的排名为
R
a
n
k
[
i
]
Rank[i]
Rank[i],则该门课产生的方案数就是枚举该门课B神的分数得到的方案数之和:
L
R
a
n
k
[
i
]
[
U
x
]
=
∑
t
=
1
U
x
t
N
−
R
a
n
k
[
i
]
(
U
x
−
t
)
R
a
n
k
[
i
]
−
1
L_{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
这个式子是关于
U
x
U_x
Ux的
N
N
N次多项式,而
U
x
Ux
Ux最大则可能达到
1
e
9
1e9
1e9,显然不可能直接来求,我们考虑拉格朗日插值.
L a g r a n g e Lagrange Lagrange插值简介
对于一个 n n n次的多项式,只需要 n + 1 n+1 n+1个点即可唯一确定,而用 n + 1 n+1 n+1个点恢复这个 n n n次多项式的方法就是 L a g r a n g e Lagrange Lagrange插值法.
设 ( x i , y i ) (x_i,y_i) (xi,yi)为我们已经获得的 N + 1 N+1 N+1个点.
定义 f i [ x ] = ∏ i ! = j x − x j x i − x j f_i[x] = \prod_{i!=j}{\frac{x-x_j}{x_i-x_j}} fi[x]=∏i!=jxi−xjx−xj
那么 L [ x ] = ∑ i = 1 N + 1 y i f i [ x ] L[x] = \sum_{i=1}^{N+1}{y_if_i[x]} L[x]=∑i=1N+1yifi[x]
如果这道题用 L a g r a n g e Lagrange Lagrange插值求得了 L [ x ] L[x] L[x],那么第 i i i门课的答案即是 L R a n k [ i ] [ U x ] 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;
}
综合起来,这道题的答案就是 a n s = d p [ M ] [ K ] ∗ ∏ i = 1 M L R a n k [ i ] [ U x ] 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 [已经没有什么好害怕的了]
知识清单
- 容斥原理
- 动态规划
题意
给出 n n n个数 a i a_i ai和 n n n个数 b i b_i bi,将 a i a_i ai和 b i b_i bi进行匹配,使得匹配中 a i > b i a_i>b_i ai>bi的对数比 a i < b i a_i<b_i ai<bi的匹配数大 k k k组.
题解
首先将 a , b a,b a,b数组排序,然后对于每个 i i i求出满足 b j < a i ( 1 ≤ j ≤ n ) b_j < a_i (1≤j≤n) bj<ai(1≤j≤n)的 j j j的个数,并记为 c n t [ i ] cnt[i] cnt[i].
定义 f [ i ] [ j ] f[i][j] f[i][j]表示仅考虑前 i i i个 a a a中,满足有 j j j对匹配使得 a u > b v a_u > b_v au>bv成立的方案数.(显然 1 ≤ j ≤ i 1≤j≤i 1≤j≤i) (注意这个定义中不要求剩下的 a a a和 b b b进行匹配.)
那么我们可以得到转移方程:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
f
[
i
−
1
]
[
j
−
1
]
∗
(
c
n
t
[
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)!表示的剩下未匹配的元素进行随机搭配的时候,有时候也会搭配出满足 a u > b v a_u > b_v au>bv的匹配,像这种就是不合法的,因此我们还要进行去重操作.
设 g [ i ] g[i] g[i]表示在 n n n个匹配中,恰好有 i i i个匹配满足 a u > b v a_u > b_v au>bv的方案数.显然这回 g [ ( n + k ) / 2 ] g[(n+k)/2] g[(n+k)/2]就是我们要的答案.
容易想到转移方程:
g
[
i
]
=
f
[
n
]
[
i
]
∗
(
n
−
i
)
!
−
g
[
i
+
1
]
∗
C
i
+
1
i
−
g
[
i
+
2
]
∗
C
I
+
2
i
−
.
.
.
−
g
[
n
]
∗
C
n
i
g[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
解释一下为什么要乘以 C j i C^{i}_{j} Cji这个系数:因为 f [ n ] [ i ] ∗ ( n − i ) ! f[n][i]*(n-i)! f[n][i]∗(n−i)!产生的满足有 j j j个 a u > b v a_u > b_v au>bv的匹配数的方案数可以由固定 i i i个匹配数来得到(剩下的 j − i j-i j−i个匹配由 ( j − i ) ! (j-i)! (j−i)!部分负责).而固定 i i i个匹配数的方案数刚好就是 C j i C^{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
≤
1
e
5
H-L ≤ 1e5
H−L≤1e5.这意味着如果在区间
[
L
,
H
]
[L,H]
[L,H]中选出
N
N
N个不完全相同的整数,那么他们的
G
C
D
≤
H
−
L
≤
1
e
5
GCD≤H-L≤1e5
GCD≤H−L≤1e5
证明:
G
C
D
(
x
,
y
)
=
G
C
D
(
x
−
y
,
y
)
<
=
x
−
y
GCD(x,y) = GCD(x-y,y) <= x-y
GCD(x,y)=GCD(x−y,y)<=x−y
如果将 [ L , H ] [L,H] [L,H]中的各个元素除以 K K K,就相当于在 ( L − 1 K , H K ] (\frac{L-1}{K},\frac{H}{K}] (KL−1,KH]中找 N N N个不完全相同的数使得其 G C D = 1 GCD = 1 GCD=1
设 F [ x ] F[x] F[x]表示满足在 ( L − 1 K , H K ] (\frac{L-1}{K},\frac{H}{K}] (KL−1,KH]中找 N N N个不完全相同的元素,使得其 G C D = 1 GCD=1 GCD=1的方案数.
显然 F [ x ] = ( [ H K x ] − [ L − 1 K x ] ) N − ( [ H K x ] − [ L − 1 K x ] ) − 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]−....
遇见这种递推式子直接倒着进行转移就可以了.
注意,如果
K
K
K 存在于区间
[
L
,
H
]
[L,H]
[L,H]之间,那么
a
n
s
+
+
ans++
ans++,因为这种属于
N
N
N个数字全部相等的情况,我们的算法是没有考虑进去的.
另外,
这题实际上可以使用莫比乌斯反演,而且莫比乌斯反演形式特别直接,但是复杂度不对.
重新定义
F
[
x
]
F[x]
F[x]为在
[
L
,
H
]
[L,H]
[L,H]中找
N
N
N个数使得其
G
C
D
=
X
GCD=X
GCD=X的方案数
我们可以得到
∑
n
∣
d
F
[
d
]
=
(
[
H
n
]
−
[
L
−
1
n
]
)
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
μ
(
d
n
)
(
[
H
d
]
−
[
L
−
1
d
]
)
N
F[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 = 1 m μ ( i n ) \sum_{i=1}^{m}\mu(in) ∑i=1mμ(in)
当 n n n存在平方因子的时候,直接输出 0 0 0作为答案,这是很显然的.
否则, n = p 1 p 2 . . . p k n = p_1p_2...p_k n=p1p2...pk,不含平方因子.
考虑区间 [ 1 , m ] [1,m] [1,m]中的数字 i i i,如果 i i i不含有因子 p 1 p_1 p1,则可以使用积性函数的性质.
即: μ ( i ∗ n ) = μ ( i ∗ n p 1 ∗ p 1 ) = − μ ( i ∗ n p 1 ) \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]中的数并不全都与 p 1 p_1 p1互质,这就会造成 μ ( i ∗ n ) = 0 \mu(i*n) = 0 μ(i∗n)=0 而 μ ( i ∗ n p 1 ) ≠ 0 \mu(i*\frac{n}{p_1}) \ne 0 μ(i∗p1n)̸=0的情况,这就要求我们用容斥的思想来去掉这部分.
如果 i i i中恰好有一个因子 p 1 p_1 p1,则 μ ( i ∗ n p 1 ) = μ ( i p 1 ∗ n ) \mu(i*\frac{n}{p_1}) = \mu(\frac{i}{p_1}*n) μ(i∗p1n)=μ(p1i∗n)
因此要减去 ∑ i = 1 ⌊ n p 1 ⌋ μ ( i n ) \sum_{i=1}^{\lfloor \frac{n}{p_1} \rfloor} \mu(in) ∑i=1⌊p1n⌋μ(in)
而如果 i i i中有多于 1 1 1个的 p i p_i pi因子,那么这部分的莫比乌斯函数值无论是在那部分中都是 0 0 0,因此不会对最终答案造成影响.
这样相当于降低了问题的规模.
我们可以列出递推式子,然后递推解决这个问题.
∑ i = 1 n μ ( i n ) = − ∑ i = 1 n μ ( i ∗ n p 1 ) + ∑ i = 1 ⌊ n p 1 ⌋ μ ( i n ) \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 = = 1 n == 1 n==1 时候,使用杜教筛得到莫比乌斯函数的前缀和
- ∑ \sum ∑的上限为 0 0 0时候,直接返回 0 0 0.
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
≤
1
0
6
)
n,(1 \le n \le 10^6 )
n,(1≤n≤106)行代码,运行一次到崩溃的时间为
r
,
1
≤
r
≤
1
0
9
r,1 \le r \le 10^9
r,1≤r≤109.
现在你可以在任意一行花费时间
p
,
1
≤
p
≤
1
0
9
p,1\le p \le 10^9
p,1≤p≤109设置一个
p
r
i
n
t
f
printf
printf语句来判断程序是否运行到这里.
请问在最坏情况下,最少需要多少时间可以定位到bug所在行.
题解
因为我们调试的时候往往会运行很多次程序,而每次运行完之后,我们都能定位bug在哪一块代码行中.
因此我们动态规划的思想得到递推公式.
记
d
p
[
n
]
dp[n]
dp[n]表示调试
n
n
n行代码所需要花的时间.
d p [ n ] = m i n 1 ≤ k ≤ n ( d p [ n k + 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 ( n 2 ) O(n^2) O(n2)的,而如果我们发现对于 [ n k + 1 ] [\frac{n}{k+1}] [k+1n]相等的 k k k,我们只取 k k k的最小值就可以了.
所以通过底数分块,我们可以求得转移的时间复杂度为 n \sqrt{n} n,而真正有效的状态数也只有 n \sqrt{n} n,所以时间复杂度上限不会超过 O ( n ) O(n) O(n).