【4.13 提高班小记】单位根&&杂题

单位根

举例来说,我们想计算下面这个式子的值:

i ( n 2 i )

那么,我们构造二项式:

( 1 + x ) n = i 0 ( n i ) x i

带入 x = 1 x = 1 ,我们得到:

2 n = i 0 ( n i ) ( 1 1 ) n = i 0 ( 1 ) i ( n i )

把这两个式子加起来:
2 n = 2 i 0 ( n 2 i )

由此可以得到:
i ( n 2 i ) = 2 n 1

那么,一个类似的问题就是,如果想计算:

i ( n 3 i )

应该怎么办呢?

在实数域上,你找不到一个和 +/- 1 有一样好的性质的数来帮你反演。

单位根

x n 1 = 0 的解 w 称为一个 n 次单位根。

对于一个单位根 w ,如果 n 是使得 w n 1 = 0 成立的最小正整数,那么 w 称为一个 n 次本原单位根。

Remarks:如果在复平面上看,单位根均匀地分布在单位圆上,设第一象限第一个单位根为 w ,那么其他的单位根都可以写为 w i 的形式。如果 ( i , n ) = d ,那么 w i ( n / d ) = w n = 1 ,而 n / d 是比 n 更小的一个正整数,所以 w i 不是n次本原单位根。

性质:

k , 1 n i = 0 n 1 w k i = [ n | k ]

证明:

如果 n | k ,那么是 n 个 1 加起来。

否则它是等比数列求和,值为:

1 n w n k w 0 w k 1 = 0

Remarks:当 n 是偶数的时候,这是 w i w i + n / 2 相互消掉的过程。

回到上一个话题,想计算:

i 0 ( n 3 i )

那么,令 w 为一个 3 次本原多项式,构造多项式:
f ( x ) = ( 1 + x ) n

f ( w ) = i 0 ( n i ) w i f ( w 2 ) = i 0 ( n i ) w 2 i f ( 1 ) = i 0 ( n i ) w 3 i

考虑 f ( 1 ) + f ( w ) + f ( w 2 ) ,我们发现,对于 ( n i ) 来说,它的系数是:

w i + w 2 i + w 3 i = 3 × [ 3 | i ]

所以, 1 3 ( f ( 1 ) + f ( w ) + f ( w 2 ) ) = a n s .

大家应该注意到,这个算法扩展性很强:

任给一个生成函数 f ( x ) ,你想知道它的所有 k 倍数次项系数之和,可以转化为代入 k 个单位根,求平均值。

eg1 (bzoj 3328)

i 0 ( n i k ) f i b i k

n 比较大,k 是几千左右,模数 P 下存在 k 次单位根。

单位根的求法:求出 P 的一个原根 g,g 可以认为是一个 P-1 次本原单位根, g ( P 1 ) / k 就是 k 次本原单位根。

Sol

设 Fib 数的转移矩阵为 M ,构造 ( I + M ) n ,其中 M j 的系数恰好为 ( n j ) M j ,(因为 I 和任意矩阵可交换,所以二项式定理适用)。把 M j 中的左上角元素取出来就是 f i b j .为了只算 k 倍数,令 f ( x ) = ( I + x M ) n ,然后带入单位根求值即可。

#include<cstdio>
#include<iostream>
using namespace std;
struct Ma{
    long long a[5][5];
};
int T,k,p,prime[10000],cnt;
long long n;
Ma x(Ma a,Ma b)
{
    Ma ans;
    for(int i=1;i<=2;i++)
        for(int j=1;j<=2;j++)
        {
            ans.a[i][j]=0;
            for(int k=1;k<=2;k++)
                ans.a[i][j]=(ans.a[i][j]+a.a[i][k]*b.a[k][j])%p;
        }
    return ans;
}
Ma M_Pow(Ma a,long long b)
{
    Ma ans=a,base=a;
    b--;
    while(b)
    {
        if(b&1) ans=x(ans,base);
        base=x(base,base);
        b>>=1;
    }
    return ans;
}
long long Pow(int a,int b)
{
    long long ans=1,base=a%p;
    while(b)
    {
        if(b&1) ans=ans*base%p;
        b>>=1;
        base=base*base%p;
    }
    return ans;
}
bool check(int g)
{
    for(int i=1;i<=cnt;i++)
        if(Pow(g,(p-1)/prime[i])==1) return false;
    return true;
}
int get_root(int p,int phi)
{
    for(int i=2;i<=phi;i++)
    {
        if(phi%i==0)
        {
            prime[++cnt]=i,phi/=i;
            while(phi%i==0) phi/=i;
        }
    }
    if(phi!=1) prime[++cnt]=phi;
    int g=2;
    while(g<p) 
    {
        if(check(g)) return g;
        g++;
    }
}
long long F(int x)
{
    Ma tmp;
    tmp.a[1][1]=tmp.a[1][2]=tmp.a[2][1]=x;
    tmp.a[2][2]=0;
    tmp.a[1][1]++,tmp.a[2][2]++;
    tmp=M_Pow(tmp,n);
    return (tmp.a[1][1]%p+p)%p;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        cnt=0;
        scanf("%lld%d%d",&n,&k,&p);
        int W=Pow(get_root(p,p-1),(p-1)/k);
        long long w=1,ans=0;
        for(int i=0;i<k;i++)
        {
            ans=(ans+F(w))%p;
            w=w*W%p;
        }
        ans=(ans*Pow(k,p-2)%p+p)%p;
        printf("%lld\n",ans);
    }
    return 0;
}

eg2

从 0 ~ (N-1) 中选出 K 个不同的数使得它们的和是 N 的倍数的方案数

K<=1000, N<=10^9, mod = 10^9+7

Sol

我们设:

f ( x , y ) = i = 0 n 1 ( 1 + x i y )

注意到 x 的次数记录了选的数的和,y 记录了个数。

问题相当于求 y k 项中,所有 n 倍数的 x 次幂 系数之和。

我们考虑用单位根反演它,带入单位根 w :

f ( w , y ) = i = 0 n 1 ( 1 + w i y )

w 是一个 d 次本原单位根 (d|n)(这里 w 会取遍所有 n 的单位根,不一定是 n 次本原,设它是 d 次本原的)
f ( w , y ) = ( i = 0 d 1 ( 1 + w i y ) ) n / d

这里有这样一个式子成立:
i = 0 d 1 ( x w i ) = x d 1

证明:因为两边是两个 d 次多项式,并且零点/首项系数相同。

我们在两边乘上一个 w X X X ,可以得到 i = 0 d 1 ( w i x 1 ) = x d 1 ,把公式带回原式,得到:

f ( w , y ) = ( + / y d + / 1 ) n / d

这时候算 y k 的系数就很容易了。

复杂度:我们只需要枚举所有 n 的因子,有 ϕ ( n / d ) n 次单位根是 d 次本原的。计算组合数可以暴力,复杂度很优秀。

EXT:选 K 个使得模 n 余 r 成立的方案数。

杂题

1 (51nod 1982)

考虑逐位确定,对给定的 N,从大到小枚举阶乘,然后试除,大概过程是:

for (int i = 100000; i >= 2; i--) {
    while (N % fact(i)) {
        N /= fact(i);
        ans.push(fact(i));
    }
}

注意到过程中没有加减法运算,那么我们可以用质因子分解来判整除。

我们把 1~N 里的所有质数取出来,设为 b 1 b k ,设 n 的形式为:

n = i b i n i

对于一个阶乘 x ! ,我们可以计算出每个质数在 x ! 的分解中的次数。

注意到这样一个事实:(x-1)! 相比于 x!,它只在 O ( log x ) 个质因子上次数发生了改变。

考虑线段树,我们对所有质数 b 1 b k 建线段树,线段树每个位置存值:n 的质因子分解中 b i 的次数 n i x ! 的分解中 b i 的次数 x i ,以及 n i x i 的值。

那么,计算 n 的分解中 x ! 出现了多少次,相当于询问 [ n i / x i ] 的最小值。为了在 n 中把 x ! 除掉,我们暴力即可。当从 x ! ( x 1 ) ! 枚举的时候,暴力修改 log x 个位置上的信息即可。

如果打标记,复杂度应该是 O ( n log n log log n ) . 因为只有 x 能整除 b i 才会修改,实际上和埃拉托斯特尼筛法复杂度一致。

2 (51nod 1325)

枚举一个根必选,然后建两个有根树,有根树上选儿子意味着选祖先,问题转化为最大权闭合图。

如果使用点分治,可以把枚举根的复杂度降一些。

3 (52nod 1355)

公式:

l c m ( S ) = T g c d ( T ) 1 | T |

它来源于:
max ( S ) = T S ( 1 ) | T | + 1 min | T |

Fib 数又有一个公式:
gcd ( f i , f j ) = f gcd ( i , j )

那么我们所求即为:
T S f gcd T ( 1 ) | T |

我们类比加法形式的:
T S f ( gcd T ) ( 1 ) T

在加法形式中,这是经典的莫比乌斯反演问题,它的一系列推导手段都可以推广到乘法上。

反演套路:找到一个 g 使得:

d | n g ( d ) = f ( n )

那么:
= T S d | gcd T g ( T ) ( 1 ) | T | = d g ( d ) T S d ( 1 ) | T |

具体计算过程中,因为权值范围很小,正确实现的复杂度为 O ( N log N ) 即可通过。

4 (bzoj 4588)

我们把所有货币从小到大排序,那么它按照整除关系形成一个链。

我们考虑从前向后枚举每种硬币进行DP。

假设排好序的硬币序列是 v i , 不妨设 v 1 = 1 .

我们设 f i ( n ) 表示用 v 1 v i 拼出 n × v i 加上 M mod v i 的零散部分的方案数,那么:

f 1 ( n ) = 1

考虑从 1 2 的转移,首先我们知道 M v 2 的余数 r 应该由 v 1 提供,除此之外, v 1 的使用次数应该是 d = v 2 / v 1 的整倍数,设:

g ( n ) = f 1 ( d n + r )

枚举 v 2 使用了多少个:
f 2 ( n ) = i n g ( i )

然后我们使用归纳法可以证明 f i ( n ) 是一个次数不超过 i 的多项式,所以你只需要维护多项式的 (i+1) 个点值 / 系数表达 / 组合数表达 即可.

州阁筛

问题: 求 π ( n ) , 它表示不超过 n 的素数个数, n 10 10 .

考虑去 DP / 筛它,我们把不超过 n 的素数取出来,设为 p 1 , p 2 p k .

f i ( n ) 表示在 [ 1 , n ] 中不被 p 1 p i 中任何一个整除的数的个数.

我们发现, f k ( n ) + k 即为所求答案.

转移:

f k ( n ) = f k 1 ( n ) f k 1 ( n / p k )

首先用 p1 ~ p[k-1] 筛,然后用 pk 筛漏掉的.

一些数论常识告诉我们,dp的第二维有用的状态只有 n 个.

直接使用这个算法进行dp,复杂度是 O ( n log n ) 并不能通过.

注意到:

n < p i + 1 时,一定有 f i ( n ) = 1 .

n < ( p i + 1 ) 2 时, [ n / p i + 1 ] < p i + 1 ,所以转移式中 f i + 1 ( n ) = f i ( n ) f i ( [ n / p i + 1 ] ) = f i ( n ) 1 ,发现转移的第二项是定值.

所以我们枚举到 p i 的时候,只需要关注 n > ( p i ) 2 的状态的转移, p i n < ( p i ) 2 这些状态的转移可以打懒标记,最后统一处理.

加上这个优化,复杂度可以降到 O ( n 3 / 4 log n ) ,可以接受.

这是一个通用的算法,也适用于求关于素数的低阶多项式的和 (和,平方和…)

5 (51nod 1860)

问题相当于求 [a,b] 里有多少数可以由 <= P 的质数表示

进一步转化,相当于求 [1,a-1] 和 [1,b] 里由多少数可以这样表示.

如果一个数 n 可以由不超过 B 的数的乘积表示,称它为 B-smooth number ,B-光滑数

我们把 1~P 里的质数拿出来,设为 p 1 , p 2 , , p k .

我们设 f i ( n ) 表示 [ 1 , n ] 有多少数能表示为 p 1 , p 2 p i 的倍数,转移:

f i ( n ) = f i 1 ( n ) + f i ( [ n / p i ] )

直接做的复杂度是 O ( P N ) ,能优化吗?

从转移式中看不出一些好用的性质,考虑倒着 DP.

g i ( n ) 表示 [ 1 , n ] p i , p i + 1 p k 的乘积能表示的数的个数,转移:

g i ( n ) = g i + 1 ( n ) + g i ( [ n / p i ] )

注意到:

n < p i 的时, g i ( n ) = 1 .

p i n < ( p i ) 2 时, g i ( n ) = g i + 1 ( n ) + g i ( [ n / p i ] ) = g i + 1 ( n ) + 1 .

发现了和例题中一样的性质,然后套路就行了.

Remark: 正着做的话,小质数很快的就能表示出区间里很多数,并且这些数的分布的规律可能并不好找. 倒着做的话,较大的质数表出的区间中的数比较稀疏,所以可以优化.


before

幂和

i = 1 n i k
.

Sol

三方做法:

构造一个向量 A = ( i 0 , i 1 , . . . i k , S U M ) .

( i + 1 ) k = t = 0 k ( k t ) i t .

发现可以通过 i 0 i k 线性表示出 ( i + 1 ) k .

所以可以构造转移矩阵

平方做法:

C(1, k) + C(2, k) + … + C(n, k) = C(n+1, k+1)

i = 0 n ( i k )

我们把 i^k 看作一个多项式,我们可以用 ( i 0 ) ( i k ) 来表示这个多项式。(它的表示系数恰好是斯特林数)。

n log n 算法

斯特林数可以使用 FFT 来预处理

线性算法

我们可以猜想,答案是一个关于 n 的 k+1 次多项式。
( k 次多项式,给它求前缀和,可以得到一个 k+1 次多项式)。

给定一个 k 次多项式 f(x) 在 0 ~ k 处的取值 (a[0~k]),我们可以使用拉格朗日插值法,把这个多项式算出来。

代数上的知识:给定一个多项式在 k+1 个点的取值,可以唯一决定一个 k 次多项式,所以我们只需要构造多项式 f,使得 f(i) = a[i].

类比中国剩余定理的做法,我们考虑:

f i ( x ) = a [ i ] ( ( x 0 ) ( x 1 ) . . . ( x ( i 1 ) ) ( x ( i + 1 ) ) . . . ( x k ) ) / ( ( i 0 ) ( i 1 ) . . . ( i ( i 1 ) ) ( i ( i + 1 ) ) . . . ( i k ) )

它的分母相当于分子带入 x=i 得到的一个式子。
它的分子相当于在 (x-0)(x-1)…(x-k) 中把 (x-i) 一项去掉

j i , f i ( j ) = 0 ( 0 <= j <= k ) f i ( i ) = a [ i ]

可以注意到这个 f_i(x) 是一个 k 次多项式(分子上乘了 k 个 x)

f ( x ) = f i ( x )

在很多场合,题目并不需要你去计算这个多项式的系数,而是要求你把某个 f(n) 算出来(已经给你 f(0) ~ f(k))的值。

考虑直接把 n 带入 f i ( x ) 求值,然后加起来即可。

注意 42 ~ 44 行的式子:

  • 分母上是一个 (i-1)! 和一个 (k-i)! ,乘上若干个 -1 (O(1)算)。
  • 分子上的话,考虑在 O(k) 的时间内预处理 (n-0) ~ (n-k) 的前缀积,后缀积,那么对于一个 f_i(n) 来说,就是一个前缀积和后缀积的乘积(除掉了 n-i 这一项) O(1) 算

这整个式子可以在 O(k) 计算,从而得到 f(n) 的值。

应用到这个题上,我们可以在 O(k) 的时间内计算出 0^k, 1^k… k^k ,求前缀和得到这个多项式在 0 ~ k 上的取值,然后套算法即可。

总结:当你注意到题目中让你计数的对象是一个关于 n 的 k 次多项式,就可以考虑暴力算出多项式的前 k+1 项,然后套用这个算法。

CodePlus 3 T4

T 次询问,每次求长度为 N+M 的、恰好有 N 个 -1 和 M 个 1 的序列的前缀最小值的期望
N, M <= 200000, T <= 200000

Sol

总方案数是 C(N+M, M)

求一个随即变量 X 的期望,如果 X 的取值范围是整数,我们可以枚举 i,计算 Pr(X>i) 或者 Pr(X

BBQ Hard

给定长度为 N 的数组 A[] 和 B[]
i j C ( A i + A j + B i + B j , A i + A j )
N <= 200000, A[i], B[i] <= 2000

Sol

所求的每一项相当于从 (-A[i], -B[i]) 移动到 (A[j], B[j]) 的方案数(每一步向右或者上)。

因为 A[i], B[i] 都很小,所以可以直接做一个 4000 * 4000 的 DP。

AGC 019 F

有一个 N+M 的序列,每个位置是 0 或者 1,且恰好有 N 个 1
你需要依次猜每个位置的值,猜对得 1 分,否则不得分。
猜过一个位置,你会马上知道它的真实值
求最优策略下的得分期望
N+M <= 1e6

Sol

最优策略下,设到现在为止剩下 x 个 1 和 y 个 0,哪个多猜哪个是比较优秀的(得分概率为 m a x ( x , y ) / ( x + y ) )。

序列是随机生成的,所以你会转移到一个剩下 x 个 1 和 y 个 0 的局面的方案数为

( x + y x ) × ( N x + M y N x ) ( N + M N )

答案为 x y ( x + y x ) × ( N x + M y N x ) ( N + M N ) × max ( x , y ) x + y ,但是这个式子的枚举量是 O(N^2) 的。

我们枚举 len=(x+y),计算倒数第 len 次猜的时候会得分的概率。

  • 定义一个局面(或者说一个状态)是被 0 支配的,当且仅当这 len 个数里 0 比 1 多(这时候,max(x,y) 中取 y)
  • 定义一个局面是被 1 支配的,当且仅当这 len 个数里 1 的个数大于等于 0(这时候,max(x,y) 中取 x)

我们从小到大枚举 len,从 len 到 len+1 的时候,绝大多数的序列被支配关系是不变的。

所以我们设几个变量,分别表示被 0 支配的序列的个数/产生的贡献和,被 1 支配的序列的个数/产生的贡献和。

从 len 转移到 len+1 的时候,只需要特殊处理被支配关系发生转变的序列(这一部分的计算不需要枚举任何东西,因为只有 0 和 1 的个数差不超过 1 的序列需要考虑)。

具体细节可以参见 AC 代码。

数论基础

  • 整数、同余、最大公约数与最小公倍数
  • 裴蜀定理,一次不定方程,中国剩余定理

裴蜀定理

形如 ax+by = c 的方程称为二元一次不定方程。

不定方程:限定了未知数只能取整数。

使得 ax+by=d 有解的、最小的正整数 d 恰好是 gcd(a,b)。
这里 x 和 y 是未知数。

ax+by=gcd(a,b) 一定有解,它的解可以通过扩展欧几里得算法找出来。

HAOI 外星人

Sol

对于一个 N,求欧拉函数 phi(N) 的过程:

  • 令 ans = N
  • 枚举 N 的每种质因子 p,令 ans = ans / p * (p - 1)

注意到:

  • 对于 N 的一个质因子 p > 2,ans / p * (p - 1) 虽然消掉了一个 p,但是带来了几个更小的质因子。这个过程会持续下去,直到变成一个 2,然后 ans / 2 可以消掉一个质因子 2 而不产生任何新的质因子。
  • 除了 N 是奇数的第一次取 phi,每一次一定会消掉一个 2。

所以我们只需要数一下有多少个 2 需要消掉即可。

威尔逊定理

我们从多项式的观点看,费马小定理告诉我们:

1 x P 1 , x P 1 1 0 ( mod P )

那么我们知道, 1 P 1 都是 x P 1 1 的根(在 mod P 下)。又因为 x P 1 1 是一个首项为 1 的 P-1 次多项式,所以有:
x P 1 1 ( x 1 ) ( x 2 ) ( x P + 1 ) ( mod P )

比较两个多项式的系数,由此得到威尔逊定理:

( P 1 ) ! 1 ( mod P )

另外一个结论:对于一个给定的质数 P ,我们定义 f ( k ) 表示从 1 P 1 中选 k 个不同的数求乘积,所有选法的乘积之和(在模 P 意义下)那么有:
f ( P 1 ) = 1 ( mod P ) 1 i < P 1 , f ( i ) 0 ( mod P )

证明:考虑 1 i P 1 ( x i ) k 次项系数。

CC Feb / Jan

给定N,定义 f(K) 为从 [1,N] 中选K个不同的整数求乘积,不同的选法的乘积之和

给定R,求 [1,R] 中有多少f(K)是给定质数P的倍数

Sol

问题相当于求 1 i N ( x i ) [ 1 , R ] 中有多少项为 0(或者说有多少项非 0)。

N = a P + b ,那么原式可以写为:

x a ( 1 i P 1 ( x i ) ) a 1 i b ( x i )

因为 1 i P 1 ( x i ) 等于 x P 1 1 ,所以上式可以写成:
x a ( x P 1 1 ) a 1 i b ( x i )

不妨设 b < P 1 ,否则右边又能凑出一个 x P 1 1

注意到上式左边只有 P 1 的整倍数有值,而右边是一个次数为 b < P 1 的多项式,所以右边乘到左边相当于把左边每个系数“复制”了若干份。

右边的多项式可以使用分治 FFT 展开计算,左边的式子可以用 Lucas 定理统计有多少非 0 项,然后两边结合即可。

Catalan 数

Catalan 数定义:满足递推公式

h 0 = 1 h n = 0 i < n h i h n i 1

的数列 { h n }

推导方法

生成函数方法

设它的生成函数为 C ( x ) ,它满足:

C ( x ) = x C ( x ) 2 + 1

解得:
C ( x ) = 1 1 4 x 2 x

使用二项式定理展开 1 4 x
(1) ( 1 4 x ) 1 2 = i ( 4 x ) i × ( 1 / 2 i ) (2) = ( 4 x ) i × 1 i ! × ( 1 / 2 ) ( 1 / 2 ) ( 3 / 2 )

思路: 4 i 拆成 2 i × 2 i ,一个 2 i 消掉之后分母上所有 2,一个 2 i 展开成 ( 2 n ) ! ! n ! (2/1 * 4/2 * 6/3 …),从而我们可以在分子分母上构造出来 ( 2 n n ) ,注意到前面有 i 个 -1,之后又有 i 1 个 -1,放在一起以后每一项都是负的。

组合方法

( 2 n n ) ( 2 n n 1 )

组合意义

  • N 条边的凸多边形三角剖分方案数
  • N 个点的二叉树的形态数
  • N 对括号的合法括号序列数
  • 1~N 依次入栈,可能的出栈序列数

两种形式

  • 维护了一个计数器,每次给他加一或者减一,不能为负/低于某个值,这一类问题常常可以用括号序计数的角度思考
  • 对一个规模为N的问题计数,它可以拆成两个规模为x和 N-x-1的问题,这一类问题常可用二叉树形态计数的角度思考,配合生成函数的卷积。

猜你喜欢

转载自blog.csdn.net/dt_kang/article/details/79944113