分析:
样例解释:
2 2
1 1 1 1
1 1 2
1 2 1
2 1 1
一个类似组合问题,可以想到生成函数
那我们先构造一个生成函数吧:
A(x)
A
(
x
)
表示氨基酸的生成函数,有一种质量为
i
i
的氨基酸,前的系数就+1
因为我们每种可以选任意个,也存在顺序问题,那么
Ai(x)
A
i
(
x
)
就是选择
i
i
个氨基酸的方案数
那么答案显然是的n次项系数
那么我们只要对多项式 1−A(x) 1 − A ( x ) 求逆,得到的多项式 B(x) B ( x ) 中 xn x n 的系数就是答案
多项式求逆
对于一个多项式 A(x) A ( x ) ,称其最高项的次数为这个多项式的度(degree),记为 degA d e g A
对于一个多项式
A(x)
A
(
x
)
,如果存在
B(x)
B
(
x
)
满足
degB<=degA
d
e
g
B
<=
d
e
g
A
,并且
A(x)B(x)≡1
A
(
x
)
B
(
x
)
≡
1
(mod
xn
x
n
)
那么称
B(x)
B
(
x
)
为
A(x)
A
(
x
)
在mod
xn
x
n
意义下的逆元,记作
A−1(x)
A
−
1
(
x
)
求解过程
现在考虑如何求
A−1(x)
A
−
1
(
x
)
,当
n=1
n
=
1
时,
A(x)≡c
A
(
x
)
≡
c
(mod x),
c
c
是一个常数,这样,就是
c−1
c
−
1
对于
n>1
n
>
1
的情况,设
B(x)=A−1(x)
B
(
x
)
=
A
−
1
(
x
)
由定义可以知道
假设在mod
x⌈n2⌉
x
⌈
n
2
⌉
意义下
A(x)
A
(
x
)
的逆元是
B'(x)
B
′
(
x
)
并且我们已经求出,那么
再将
(1)
(
1
)
放到mod
x⌈n2⌉
x
⌈
n
2
⌉
意义下
然后
(2)−(3)
(
2
)
−
(
3
)
就可以得到
两边平方
这里解释一下平方后为什么模的
x⌈n2⌉
x
⌈
n
2
⌉
也会平方
左边多项式在
mod⋅xn
m
o
d
·
x
n
意义下为
0
0
,那么就说明其到
n−1
n
−
1
次项系数都为
0
0
平方了之后,对于第项,其系数
ai
a
i
为
∑ij=0ajai−j
∑
j
=
0
i
a
j
a
i
−
j
,很明显
j
j
和之间必然有一个值小于
n
n
,因此必然是
0
0
,也就是说平方后在意义下仍然为
0
0
然后同时乘上(
A(x)B(x)≡1(mod⋅xn)
A
(
x
)
B
(
x
)
≡
1
(
m
o
d
·
x
n
)
),移项可以得到
这样就可以得到mod
xn
x
n
意义下的逆元了,利用FFT/NTT加速之后可以做到在
O(nlogn)
O
(
n
l
o
g
n
)
时间内解决当前问题,最后总的时间复杂度也就是
顺便一提,由这个过程可以看出,一个多项式有没有逆元完全取决于其常数项是否有逆元
声明一下,1005060097的原根是5
在FFT中,有原根
wn=e2πin=(cos(2πn),sin(2πn)i)
w
n
=
e
2
π
i
n
=
(
c
o
s
(
2
π
n
)
,
s
i
n
(
2
π
n
)
i
)
NTT中,我们设
G
G
为的原根,会发现
GP−1n
G
P
−
1
n
具有和
wn=e2πin
w
n
=
e
2
π
i
n
相似的性质
目前求原根的方法就是暴力啊:
int cal(ll x) {
if (x==2) return 1;
//暴力判断g^(p-1)同余于1(mod p)是否当且仅当指数为p-1的时候成立
for (int i=2;i;i++) {
bool flag=1;
for (ll j=2;j*j<x;j++)
if (KSM(i,(x-1)/j)==1) {
//g^((p-1)/n)
flag=0;
break;
}
if (flag) return i;
}
}
下面给出的 (r∗2k+1) ( r ∗ 2 k + 1 ) 原根表
r∗2k+1 r ∗ 2 k + 1 | r | k | g |
---|---|---|---|
3 | 1 | 1 | 2 |
5 | 1 | 2 | 2 |
17 | 1 | 4 | 3 |
97 | 3 | 5 | 5 |
193 | 3 | 6 | 5 |
257 | 1 | 8 | 3 |
7681 | 15 | 9 | 17 |
12289 | 3 | 12 | 11 |
40961 | 5 | 13 | 3 |
65537 | 1 | 16 | 3 |
786433 | 3 | 18 | 10 |
5767169 | 11 | 19 | 3 |
7340033 | 7 | 20 | 3 |
23068673 | 11 | 21 | 3 |
104857601 | 25 | 22 | 3 |
167772161 | 5 | 25 | 3 |
469762049 | 7 | 26 | 3 |
998244353 | 119 | 23 | 3 |
1004535809 | 479 | 21 | 3 |
2013265921 | 15 | 27 | 31 |
2281701377 | 17 | 27 | 3 |
3221225473 | 3 | 30 | 5 |
tip
注意时刻取模,防止爆ll
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const ll p=1005060097;
const ll o=5; //原根
const int N=300010;
int n,m,fn;
ll a[N],b[N],c[N];
ll KSM(ll a,ll b) {
ll t=1;
a%=p;
while (b) {
if (b&1) t=(t*a)%p;
b>>=1;
a=(a*a)%p;
}
return t%p;
}
void NTT(int n,ll *a,int opt) {
int i,j=0,k;
for (i=0;i<n;i++) {
if (i>j) swap(a[i],a[j]);
for (int l=n>>1;(j^=l)<l;l>>=1);
}
for (i=2;i<=n;i<<=1) {
int m=i>>1;
ll wn=KSM(o,(p-1)/i);
for (j=0;j<n;j+=i) {
ll w=1;
for (k=0;k<m;k++,w=(w*wn)%p) {
ll z=(a[j+k+m]*w)%p;
a[j+m+k]=(a[j+k]-z+p)%p;
a[j+k]=(a[j+k]+z)%p;
}
}
}
if (opt==-1) reverse(a+1,a+n);
}
void inv(int n,ll *a,ll *b,ll *c) {
if (n==1) {
b[0]=KSM(a[0],p-2);
return;
}
inv(n>>1,a,b,c);
int k=n<<1;
for (int i=0;i<n;i++) c[i]=a[i];
for (int i=n;i<k;i++) c[i]=0;
NTT(k,c,1); NTT(k,b,1);
for (int i=0;i<k;i++) b[i]=(2-c[i]*b[i]%p+p)%p*b[i]%p;
NTT(k,b,-1);
ll inv=KSM(k,p-2);
for (int i=0;i<n;i++) b[i]=(b[i]*inv)%p; // /fn
for (int i=n;i<k;i++) b[i]=0;
}
int main()
{
scanf("%d%d",&n,&m);
int mx=0;
for (int i=1;i<=m;i++) {
int x;
scanf("%d",&x);
if (x<=n) a[x]=a[x]-1+p; //1-A[x]
mx=max(mx,x);
}
a[0]=1;
fn=1;
while (fn<=mx*2) fn<<=1;
inv(fn,a,b,c);
printf("%lld\n",b[n]%p);
return 0;
}