P's Blog

  • Home

  • About

  • Tags

  • Categories

  • Archives

  • Search

NumberTheory

Posted on 2019-08-17 | Edited on 2019-09-30 | In 模板 | Views:

NumberTheory

快速幂

  • 模数在 $int$ 范围

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ll qPow(ll a, ll b, ll c) {  //求(a^b) % c
    ll ret = 1;
    while (b) {
    if (b & 0x1) ret = ret * a % c;
    a = a * a % c;
    b >>= 1;
    }
    return ret;
    }
  • 模数超出 $int$ 范围

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    ll qMulti(ll x,ll y,ll n){ //求(x*y)%n
    ll ret=0,tmp=x%n;
    while(y) {
    if(y&0x1)if((ret+=tmp)>n)ret-=n;
    if((tmp<<=1)>n)tmp-=n;
    y>>=1;
    }
    return ret;
    }
    //O(1)快速乘
    ll qMulti(ll x,ll y,ll mod){
    return (x*y-(ll)((long double)x/mod*y)*mod+mod)%mod;
    }
    ll qPow(ll a,ll b,ll c){ //求(a^b) % c ,c超出int范围
    ll ret=1;
    while(b) {
    if(b&0x1)ret=qMulti(ret,a,c);
    a=qMulti(a,a,c);
    b>>=1;
    }
    return ret;
    }
  • $\mathcal{O}(1)$ 快速幂

    1
    2
    3
    4
    5
    6
    int step;
    ll pre1[N];//pre1[i]表示a^{i*step}
    ll pre2[N];//pre2[i]表示a^{i}
    ll qPow(ll a,ll b,ll c){ //求(a^b) % c
    return pre1[c/step]*pre2[c%step]%c;
    }

欧几里得算法

gcd

1
2
3
ll gcd(ll x,ll y){
return y?gcd(y, x%y):abs(x);
}

exgcd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ll exgcd(ll a, ll b, ll &x, ll &y){ //求解ax+by=gcd(a,b),返回gcd(a,b)
if(b==0){
x = 1;
y = 0;
return a;
}
ll r = exgcd(b, a%b, y, x);
y -= a/b*x; //这里已经是递归,回溯的过程了,x,y已经颠倒了
return r;
}
/*
x = c/gcd(a,b)*x0 + b/gcd(a,b)*t;
y = c/gcd(a,b)*y0 - a/gcd(a,b)*t;
(x0, y0 为方程的一组特解, t为整数)
*/

裴蜀定理

$ax+by=c$ 有解的充要条件为 $gcd(a,b) | c$

推广: $a_1x_1+a_2x_2+\cdots+a_nx_n=c$ 有解的充要条件为 $gcd(a_1,a_2,\cdots,a_n) | c$

类欧问题

素数和因子

打表

1
2
3
4
5
6
7
8
9
10
11
12
13
bool notP[N];
int prime[N],num_prime=0;
void get_prime(){
notP[1]=1;
for(int i=2;i<N;i++){
if(!notP[i]) prime[num_prime++]=i;
for(int j=0;j<num_prime&&(ll)i*prime[j]<N;j++){
int k = i*prime[j];
notP[k] = 1;
if(i % prime[j] == 0) break;
}
}
}

区间素数

1
2
3
4
5
6
7
8
memset(visab,0,sizeof(visab));
for (int i = 0; i < cnt && prime[i] <= b; i++) {
LL k = a / prime[i];
if (k * prime[i] < a) k++;
for (LL j = k * prime[i]; j <= b; j += prime[i]) {
visab[j - a] = 1;
}
}

唯一分解定理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
pair<ll,int> factor[50];
int num_factor = 0;
void decomposition(ll n) {
num_factor = 0;
for (int i = 0; i < num_prime && (ll)prime[i] * prime[i] <= n && n > 1;++i) {
if (n % prime[i] == 0) {
int cnt=0;
while (n % prime[i] == 0 ) {
n /= prime[i];
cnt++;
}
factor[num_factor++] = pair<ll,int>(prime[i],cnt);
}
}
if (n > 1) factor[num_factor++] = pair<ll,int>(n,1);
}

pair<ll,int> factor[50];
int num_factor = 0;
void decomposition(ll n) {
num_factor = 0;
int m=(int)sqrt(n+0.5);
for(int i=2;i<=m;i++){
if(n%i==0){
factor[num_factor]=pair<ll,int>(i,0);
while(n%i==0){
factor[num_factor].se++;
n/=i;
}
num_factor++;
}
}
if (n > 1) factor[num_factor++] = pair<ll,int>(n,1);
}

n 的因子数

分解 n,$\large n=p_1^{a_1}*p_2^{a_2}\cdots p_m^{a_m}$,n 的因子和为

n 的因子和

分解 n,$\large n=p_1^{a_1}*p_2^{a_2}\cdots p_m^{a_m}$,n 的因子和为

1-n的因子数

$\Large\sum\limits_{i=1}^n\lfloor\frac n i\rfloor$

n=12

$\large\lfloor\frac n i\rfloor$ [l,r]
12 [1,1]
6 [2,2]
4 [3,3]
3 [4,4]
2 [5,6]
1 [7,12]

每个 $l$ 等于上一个 $r+1$,$r=n/(n/l)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//solution1
ll res = 0;
for (int l = 1; l <= n; l = r + 1) {
int r = n / (n / l);
res += (n / l) * (r - l + 1);
}
//solution2
int k=sqrt(n+0.1);
for (int i = 1; i <= k; i++) {
ans+=n/i;
if (n/(i+1)<i)break;
else ans+=(n/i-n/(i+1))*i;
}
//solution3
for (int i = 1; i <= k; i++) ans+=n/i;
ans*=2;
ans-=k*k;

1-n的因子之和

统计 1-n 每个数取的次数$\Large\frac n i$,即$\Large\sum\limits_{i=1}^n\lfloor\frac n i\rfloor*i$

1
2
3
4
5
ll res = 0;
for (int l = 1; l <= n; l = r + 1) {
int r = n / (n / l);
res += (n / l) * (r + l) * (r - l + 1) / 2;
}

费马小定理

威尔逊定理

RSA

两个大素数密钥 $p,q$ ,令 $n=p\times q$ ,随机取加密密钥 $e$ ,使得 $gcd(e,(p-1)(q-1))=1$ ,使用 $exgcd$ 计算解密密钥 $d=e^{-1}\mod (p-1)(q-1)$ , $d$ 与 $n$ 也是互质的。 $e$ 和 $n$ 是公开密钥, $d$ 是私人密钥。

设 $A$ 为明文, $B$ 为密文

积性函数

  1. $f[1]=1$
  2. $设n=\prod p_i^{k_i},则f(n)=f(\prod p_i^{k_i})=\prod f(p_i^{l_i})$
  3. $设n=\prod p_i^{k_i},则f(n)=f(\prod p_i^{k_i})=\prod f(p_i)^{k_i}$ 若满足这一项称为完全积性函数。

欧拉函数

  • $1\sim n$中与 n 互质的个数(包括 1 )

  • 积性

  • $prime[i]\le x<prime[i+1]$,必定有$\phi(x)\le prime[i]$

  • 欧拉定理

  • 扩展欧拉定理

  • $1\sim n$中与 n 互质的数的和为$\large \frac {n*\phi(n)} 2+\epsilon(n)$

    设 gcd(n,p)=1,由更相减损术得 gcd(n-p,p)=1,且一定有p≠n-p,故 1~n 中与 n 互质的数可以由此两两配对,而每一对的和都为 n,所以总和为 n*φ(n)/2

  • 求欧拉函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    int euler(int n){
    int ans=1;
    for (int i=0; i<prime.size()&&prime[i]*prime[i]<=n&&n!=1; i++) {
    int e=0;
    while (n%prime[i]==0&&n!=1) {
    e++;
    n/=prime[i];
    }
    if (e) ans*=pow(prime[i], e-1)*(prime[i]-1);
    }
    if (n>1) ans*=(n-1);
    return ans;
    }
    O(sqrt(n))
    int euler(int n){
    int m=(int)sqrt(n+0.5);
    int ans=n;
    for(int i=2;i<=m;i++){
    if(n%i==0){
    ans=ans/i*(i-1);
    while(n%i==0) n/=i;
    }
    }
    if(n>1) ans=ans/n*(n-1);
    return ans;
    }
  • 欧拉函数打表

    • 直接打表

      1
      2
      3
      4
      5
      6
      for(i=1; i<=maxn; i++) p[i]=i;
      for(i=2; i<=maxn; i+=1)
      if(p[i]==i){
      for(j=i; j<=maxn; j+=i)
      p[j]=p[j]/i*(i-1);
      }
    • 利用线筛打表O(n)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      bool notPrime[N+1];
      int prime[N+1],num_prime=0,phi[N];
      void get_prime(){
      notPrime[1]=1;
      for(int i=2;i<=N;i++){
      if(!notPrime[i]) {
      prime[num_prime++]=i;
      phi[i]=i-1;
      }
      for(int j=0;j<num_prime&&(ll)i*prime[j]<=N;j++){
      notPrime[i*prime[j]] = 1;
      if(i%prime[j] == 0) {
      phi[i*prime[j]]=phi[i]*prime[j];
      break;
      }else phi[i*prime[j]]=phi[i]*(prime[j]-1);
      }
      }
      }

莫比乌斯函数

  • 打表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    bool notPrime[N+1];
    int prime[N+1],num_prime=0,miu[N];
    void get_prime(){
    notPrime[1]=1;
    for(int i=2;i<=N;i++){
    if(!notPrime[i]) {
    prime[num_prime++]=i;
    miu[i]=-1;
    }
    for(int j=0;j<num_prime&&(ll)i*prime[j]<=N;j++){
    notPrime[i*prime[j]] = 1;
    if(i%prime[j] == 0) {
    miu[i*prime[j]]=0;
    break;
    }else miu[i*prime[j]]=miu[i]*(-1);
    }
    }
    }

除数函数

一些完全积性函数

  • 常函数

  • 单位函数

  • 幂函数

  • 狄利克雷卷积单位函数

卷积

乘积函数

Dirichlet卷积函数

一些卷积

莫比乌斯反演

取模

除法取模

  • b和m不互质

    $\frac a b \%m =\frac{a \% (bm)}b$

    证:

    设$\frac a b \%m=r$

    $\frac a b =km+r$

    $a=kbm+br$

    $br=a-kbm$

    $r=\frac{a \% (b*m)}b$

  • 逆元( inv )

    $\frac a b \%m$时,因 $b$ 可能会过大,会出现爆精度的情况,所以需变除法为乘法,设 $c$ 是 $b$ 的逆元,则有 $b*c≡1(mod m)$,即 $\frac a b$ 的模等于 $a\times inv(b)$ 的模;

    • $m$ 为素数,根据费马小定理 $c=b^{m-2}(\mod m)$ ;

    • $b*c≡1(\mod m)$ 使用扩展欧几里得算法

    • 逆元打表

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      //求1,2,...,N关于mod的逆元(mod为质数)
      ll inv[N];
      inv[1] = 1;
      for (int i = 2; i < N; i++) inv[i] = inv[mod % i] * (mod - mod / i) % mod;
      /*证明 : i* inv[i] = inv[mod % i] * ((i - 1) * mod + mod - mod / i * i) % mod;
      i* inv[i] = inv[mod % i] * ((i - 1) * mod + mod % i) % mod;
      i* inv[i] = (inv[mod % i] * (i - 1) * mod + 1) % mod;
      i* inv[i] = 1 % mod;*/

      //求N!关于mod的逆元(mod为质数)
      ll inv[N];
      inv[N-1] = qPow(fac[N-1], mod - 2, mod);
      for (int i = N - 2; i >= 0; i--) inv[i] = (inv[i + 1] * (i + 1)) % mod;
      /*证明 : inv[i + 1] * (i + 1) != 1 % mod;
      inv[i + 1] * (i + 1) * i != 1 % mod;*/

组合数取模

  • n,m 1e5 预处理出逆元和阶乘

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ll fac[N],inv[N];
    ll C(ll a,ll b){
    if(b>a)return 0;
    return fac[a]*inv[b]%mod*inv[a-b]%mod;
    }
    void init(){//快速计算阶乘的逆元
    fac[0]=fac[1]=1;
    for(int i=2;i<N;i++)fac[i]=fac[i-1]*i%mod;
    inv[N-1] = qPow(fac[N-1], mod - 2, mod);
    for (int i = N - 2; i >= 0; i--) inv[i] = (inv[i + 1] * (i + 1)) % mod;
    }
  • n 1e9 , m 1e5

    1
    2
    for(int i=1;i<=m;i++)
    ans=ans*(n-i+1)%mod*qPow(i,mod-2,mod)%mod;
  • n,m 1e18 , mod 1e5,素数 卢卡斯定理(Lucas)

    1
    2
    3
    4
    5
    6
    ll Lucas(ll n,  ll m){
    if(m==0) return C(n, m);
    return C(n % mod, m % mod) * Lucas(n / mod, m / mod)%mod;
    }
    /*表达式:C(n,m)%p=C(n/p,m/p)*C(n%p,m%p)%p。(可以递归)
    递归方程:(C(n%p, m%p)*Lucas(n/p, m/p))%p。(递归出口为m==0,return 1)*/
  • exLucas

    求解 $C_n^m \mod p$ , $p$ 不是素数

    • 分解 $\large p=\prod p_i^{k_i}$ ,分别求 $C_n^m\mod p_i^{k_i}$ ,使用中国剩余定理合并

    • 求解 $C_n^m\mod p_i^{k_i}$

      易证 $k1-k2-k3\ge0$

    • 求解 $f(n)=\large\frac {n!}{p^a}\mod p^k$ ,分三部分

      • $1\sim n$ 中拥有 $p$ 因子的数的贡献:$f(\lfloor\frac n p\rfloor)$
      • $\Large\prod\limits_{i=1,(i,p)=1}^{p^k}i^{\lfloor\frac n {p^k}\rfloor}$
      • $\Large\prod\limits_{i=1,(i,p)=1}^{n\mod p^k}i$
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      ll exgcd(ll a, ll b, ll &x, ll &y){ //求解ax+by=gcd(a,b),返回gcd(a,b)
      if(b==0){
      x = 1;
      y = 0;
      return a;
      }
      ll r = exgcd(b, a%b, y, x);
      y -= a/b*x; //这里已经是递归,回溯的过程了,x,y已经颠倒了
      return r;
      }
      ll Chinese_Remainder(ll a[],ll m[],int len){//中国剩余定理 a存放余数 m存放两两互质的数
      ll M=1,ret=0;
      for(int i = 0; i < len; i++) M *= m[i];
      for(int i = 0; i < len; i++){
      ll Mi = M/m[i],x,y;
      exgcd(m[i],Mi,x,y);
      ret = (ret+y*Mi*a[i])%M;
      }
      return (M+ret%M)%M;
      }
      ll qPow(ll a, ll b, ll c) { //求(a^b) % c
      ll ret = 1;
      while (b) {
      if (b & 0x1) ret = ret * a % c;
      a = a * a % c;
      b >>= 1;
      }
      return ret;
      }
      ll pre[N],cnt;
      ll fac(ll n,ll pk,ll p){
      if(n<p)return pre[n];
      cnt+=n/pk;
      return pre[n%pk]*fac(n/p,pk,p)%pk;
      }
      ll vp(ll n,ll p){
      ll ans=0;
      while(n){
      ans+=n/p;
      n/=p;
      }
      return ans;
      }
      ll C(ll n,ll m,ll pk,ll p){
      pre[0]=1;
      for(int i=1;i<=pk;i++){
      pre[i]=pre[i-1];
      if(i%p)pre[i]=pre[i-1]*i%pk;
      }
      cnt=0;
      ll ans=fac(n,pk,p)*qPow(pre[pk],cnt,pk)%pk*qPow(p,vp(n,p)-vp(m,p)-vp(n-m,p),pk)%pk;
      ll x,y;
      cnt=0;
      exgcd(fac(m,pk,p)*qPow(pre[pk],cnt,pk)%pk,pk,x,y);
      ans=ans*x%pk;
      cnt=0;
      exgcd(fac(n-m,pk,p)*qPow(pre[pk],cnt,pk)%pk,pk,x,y);
      ans=ans*x%pk;
      return ans;
      }
      ll exLucas(ll n,ll m,ll p){
      ll a[100],b[100];
      int sq=sqrt(p+0.1);
      int tot=0;
      for(int i=2;i<=sq&&p>1;i++){
      int pk=1;
      while(p%i==0){
      pk*=i;
      p/=i;
      }
      if(pk>1){
      a[tot]=C(n,m,pk,i);
      b[tot++]=pk;
      }
      }
      if(p>1){
      a[tot]=C(n,m,p,p);
      b[tot++]=p;
      }
      return Chinese_Remainder(a,b,tot);
      }
  • $n!$ 中 $p$ (素数) 的幂次

  • 库默尔定理

    设 $m,n(\ge0)$,$p$ 为素数,则 $v_p(C_{m+n}^m)$ 等于在 $p$ 进制下 $m+n$ 时进位次数。

连分数

记为

佩尔方程

$x^2-dy^2=1$

  • 定理1: 对于正整数 p,q ,如果有则 $\frac p q$ 必为 a 的一个渐进值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//1
//求出p2 - D * q2 = 1的基解(最小正整数解),这个可能溢出,有必要的话,用java, 写成类比较好
bool PQA(LLI D, LLI &p, LLI &q) {//来自于PQA算法的一个特例
LLI d = sqrt(D);
if ((d + 1) * (d + 1) == D) return false;
if (d * d == D) return false;
if ((d - 1) * (d - 1) == D) return false;//这里是判断佩尔方程有没有解
LLI u = 0, v = 1, a = int(sqrt(D)), a0 = a, lastp = 1, lastq = 0;
p = a, q = 1;
do {
u = a * v - u;
v = (D - u * u) / v;
a = (a0 + u) / v;
LLI thisp = p, thisq = q;
p = a * p + lastp;
q = a * q + lastq;
lastp = thisp;
lastq = thisq;
} while ((v != 1 && a <= a0));//这里一定要用do~while循环
p = lastp;
q = lastq;
//这样求出后的(p,q)是p2 – D * q2 = (-1)k的解,也就是说p2 – D * q2可能等于1也可能等于-1,如果等于1,(p,q)就是解,如果等于-1还要通过(p2 + D * q2,2 * p * q)来求解,如下
if (p * p - D * q * q == -1) {
p = lastp * lastp + D * lastq * lastq;
q = 2 * lastp * lastq;
}
return true;
}

//2
typedef long long ll;
ll a[20000];
bool pell_minimum_solution(ll n,ll &x0,ll &y0){
ll m=(ll)sqrt((double)n);
double sq=sqrt(n);
int i=0;
if(m*m==n)return false;//当n是完全平方数则佩尔方程无解
a[i++]=m;
ll b=m,c=1;
double tmp;
do{
c=(n-b*b)/c;
tmp=(sq+b)/c;
a[i++]=(ll)(floor(tmp));
b=a[i-1]*c-b;
//printf("%lld %lld %lld\n",a[i-1],b,c);
}while(a[i-1]!=2*a[0]);
ll p=1,q=0;
for(int j=i-2;j>=0;j--){
ll t=p;
p=q+p*a[j];
q=t;
//printf("a[%d]=%lld %lld %lld\n",j,a[j],p,q);
}
if((i-1)%2==0){x0=p;y0=q;}
else{x0=2*p*p+1;y0=2*p*q;}
return true;
}

int main(){
ll n,x,y;
while(~scanf("%lld",&n)){
if(pell_minimum_solution(n,x,y)){
printf("%lld^2-%lld*%lld^2=1\t",x,n,y);
printf("%lld-%lld=1\n",x*x,n*y*y);
}
}

中国剩余定理

  • 互质

    假设整数$m_1,m_2,\cdots,m_n$两两互质,则对任意的整数$a_1,a_2,\cdots,a_n$ 方程组$(S)$有解

    设

    通解形式为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ll Chinese_Remainder(ll a[],ll m[],int len){//中国剩余定理  a存放余数  m存放两两互质的数
    ll M=1,ret=0;
    for(int i = 0; i < len; i++) M *= m[i];
    for(int i = 0; i < len; i++){
    ll Mi = M/m[i],x,y;
    exgcd(m[i],Mi,x,y);
    ret = (ret+y*Mi*a[i])%M;
    }
    return (M+ret%M)%M;
    }
  • 非互质

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /*采用两两合并的思想
    x=a1+m1*x1
    x=a2+m2*x2
    得到
    a1+m1*x1 = a2+m2*x2 → m1*x1+m2*x2 = a2-a1
    再通过扩展欧几里得算法解出x1的最小正整数解,代入
    x=a1+m1*x1
    得到x后合并为一个方程的结果为
    y ≡ x(mod lcm(m1,m2))
    */
    pair<ll, ll> linear(ll B[], ll M[], ll n){//求解 x = B[i] (mod M[i]),总共n个线性方程组
    ll x = 0, m = 1;
    for(int i = 0; i < n; i ++) {
    ll a = m, b = B[i] - x, d = gcd(M[i], a);
    if(b % d != 0) return pair<ll, ll>(-1, -1);//答案不存在,返回-1
    ll q ,w;
    exgcd(a/d, M[i]/d, q, w);
    ll t = b/d * q%(M[i]/d);
    x = x + m*t;
    m *= M[i]/d;
    }
    x = (x % m + m ) % m;
    return pair<ll, ll>(x, m);//返回的x就是答案,m是最后的lcm值
    }

整数拆分

  1. 将n划分成若干正整数(可相同)之和的划分数。

    • dp

      $dp[n][m]= dp[n][m-1]+ dp[n-m][m]$, $dp[n][m]$表示整数 n 的划分中,每个数不大于 m 的划分数。 划分分为两种情况:

      a.划分中每个数都小于 m,相当于每个数不大于 m - 1, 故划分数为$dp[n][m-1]$

      b.划分中有一个数为 m. 那就在 n中减去 m ,剩下的就相当于把 n-m 进行划分, 故划分数为 $dp[n-m][m]$;

    • dp

      $dp[n][k]= dp[n-k][k]+ dp[n-1][k-1]$ ,$dp[n][k]$表示将n划分成k个正整数之和的划分数,划分分为两种情况:

      a.不含 1 的分法,为保证每份都 >= 2,可以先拿出 k 个 1 分到每一份,然后再把剩下的 n-k 分成 k 份即可,分法有: $dp[n-k][k]$

      b.至少有一份为 1 的分法,可以先那出一个 1 作为单独的1份,剩下的 n- 1 再分成 k- 1 份即可,分法有:$dp[n-1][k-1]$

    • 五边形数

      其中 $n-\frac 1 2k(3k-1)>=0 , n-\frac 1 2k(3k+1)>=0$

  2. 将n划分成若干正整数(不同)之和的划分数

    • O(n^2)

      $dp[n][m]= dp[n][m-1]+ dp[n-m][m-1]$,$dp[n][m]$表示整数 n 的划分中,每个数不大于m且两两不同的划分数。 划分分为两种情况:

      a.划分中每个数都小于m,相当于每个数不大于 m-1,划分数为 $dp[n][m-1]$.
      b.划分中有一个数为 m.在n中减去m,剩下相当对n-m进行划分,并且每一个数不大于m-1,故划分数为 $dp[n-m][m-1]$

  • O(n^1.5)

    $dp[i][j]$代表i个数相加等于j ,由于有不同的数最多不超过O(sqrt(n))算法个,则可以优化 $dp[i][j] = dp[i-1][j-i] + dp[i][j-i]$ ,这个递推方程的转移含义是i-1个数每个数都加1,最后再添上一个1,就从$dp[i-1][j-i]$转到$dp[i][j]$,还有就是i个数每个数都加1,就从$dp[i][j-i]$转到$dp[i][j]$

  1. 将n划分成若干奇正整数之和的划分数.

    • 法一

      $g[i][j]$: 将i划分为j个偶数

      $f[i][j]$: 将i划分为j个奇数

      $g[i][j] = f[i - j][j]$;

      $f[i][j] = f[i - 1][j - 1] + g[i - j][j]$;

    • 法二

      奇数和$dp[i][j]$为划分最大的奇数不超过j的数,$dp[i][j]=dp[i-n(j)][j]+dp[i][j-2]$,n(j)为不超过j的最大奇数,初始条件,$dp[i][1]=1$,j为偶数时候$dp[i][j]=dp[i][j-1]$;当i==n(j) ,出现$dp[0][j]$,也就是当i为奇数时候,$dp[0][j]=1$;

  2. 其他

    n的分拆数中最大部分为m的个数=把n分拆成m部分的个数

    Image-7800734

    n的分拆数中每一部分都小于等于m的个数=把n分成m份或更小
    n的分拆数中每部分的数两两不同的个数等于全部都是奇数的个数
    n的分拆数中每部分的数都相等的个数=n 的因子个数
    n的分拆数中每部分都是1或2(或者把n分拆成1或2部分)的个数=floor(n/2+1)
    n的分拆数中每部分都是1或2或3(或者把n分拆成1或2或3部分)的个数=(n+3)^2/12
    n的分拆数中每部分都>=k的个数为:f[n]-f[n-1]-f[n-2] · · · +f[n-k] (f[n]表示将n划分成若干正整数(可相同)之和的划分数)

约瑟夫环

令$f[1]$表示$n-m+1$个人玩游戏报 k 退出,第 1 个人退出的编号(下标从 0 开始);

$f[m]$表示 n 个人玩游戏报 k 退出,第 m 个人退出的编号,

注:当 m<<n 的时候,可以用等差数列的性质,加速运算.

高斯消元

整数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const int MAXN = 300; //有 equ 个方程,var 个变元。增广矩阵行数为 equ, 列数为 var+1, 分别为 0 到 var 
int equ,var;
int a[MAXN][MAXN]; //增广矩阵
int x[MAXN]; //解集
int free_x[MAXN];//用来存储自由变元(多解枚举自由变元可以使用)
int free_num;//自由变元的个数

//返回值为 -1 表示无解,为 0 是唯一解,否则返回自由变元个数
int Gauss(){
int max_r,col,k;
free_num = 0;
for(k = 0, col = 0 ; k < equ && col < var ; k++, col++){
max_r = k;
for(int i = k+1;i < equ;i++){
if(abs(a[i][col]) > abs(a[max_r][col])) max_r = i;
}
if(a[max_r][col] == 0){
k--;
free_x[free_num++] = col;//这个是自由变元
continue;
}
if(max_r != k){
for(int j = col; j < var+1; j++) swap(a[k][j],a[max_r][j]);
}
for(int i = k+1;i < equ;i++){
if(a[i][col] != 0){
for(int j = col;j < var+1;j++)a[i][j] ^= a[k][j];
}
}
}
for(int i = k;i < equ;i++) if(a[i][col] != 0)return -1;//无解
if(k < var) return var-k;//自由变元个数
//唯一解,回代
for(int i = var-1; i >= 0;i--){
x[i] = a[i][var];
for(int j = i+1;j < var;j++)
x[i] ^= (a[i][j] && x[j]);
}
return 0;
}

实数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//1
const int num = 100;
double matrix[num][num + 1]; //系数矩阵, 从 0 开始
double ans[num]; //结果数组
void exchange_col(int p1,int p2,int n) {//交换 p1 行和 p2 行的所有数据
double t;
int i;
for(i = 0 ; i <= n ; i++) t = matrix[p1][i],matrix[p1][i] = matrix[p2][i],matrix[p2][i] = t;
}
bool gauss(int n){ //求解系数矩阵为 n 的线性方程组
int i,j,k,p;
double r;
for(i = 0 ; i < n - 1 ; i++) {
p = i;
for(j = i + 1 ; j < n ; j++) { //寻找 i 列绝对值最大值位置
if(abs(matrix[j][i]) > abs(matrix[p][i])) p = j;
}
if(p != i) exchange_col(i,p,n);
if(matrix[i][i] == 0) return false;
for(j = i + 1 ; j < n ; j++) { //剩余列进行消元
r = matrix[j][i] / matrix[i][i];
for(k = i ; k <= n ; k++) matrix[j][k] -= r * matrix[i][k];
}
}
for(i = n - 1 ; i >= 0 ; i--) { //获得结果
ans[i] = matrix[i][n];
for(j = n - 1 ; j > i ; j--) ans[i] -= matrix[i][j] * ans[j];
if(matrix[i][i] == 0) return false;
ans[i] /= matrix[i][i];
}
return true;
}

//kuangbin
#define eps 1e−9
const int MAXN=220;
double a[MAXN][MAXN],x[MAXN];//方程的左边的矩阵和等式右边的值, 求解之后 x 存的就是结果
int equ,var;//方程数和未知数个数
int Gauss(){ /* * 返回 0 表示无解,1 表示有解 */
int i,j,k,col,max_r;
for(k=0,col=0;k<equ&&col<var;k++,col++){
max_r=k;
for(i=k+1;i<equ;i++) if(fabs(a[i][col])>fabs(a[max_r][col])) max_r=i;
if(fabs(a[max_r][col])<eps)return 0;
if(k!=max_r){
for(j=col;j<var;j++) swap(a[k][j],a[max_r][j]);
swap(x[k],x[max_r]);
}
x[k]/=a[k][col];
for(j=col+1;j<var;j++)a[k][j]/=a[k][col];
a[k][col]=1;
for(i=0;i<equ;i++)
if(i!=k){
x[i]-=x[k]*a[i][col];
for(j=col+1;j<var;j++)a[i][j]-=a[k][j]*a[i][col];
a[i][col]=0;
}
}
return 1;
}

母函数

可以看出:$x^2$项的系数$a_1a_2+a_1a_3+ · · · +a_{n-1}a_n$中所有的项包括n个元素$a_1,a_2,· · · ,a_n$中取两个组合的全体,同理:$x^3$项系数包含了从n个元素$a_1,a_2,· · · ,a_n$中取3个元素组合的全体,以此类推。

栗子:
整数拆分
由a,b,c,d….组成n

$G(x)=(1+x^a+x^{2a}+x^{3a}+ · · · )(1+x^b+x^{2b}+x^{3b}+ · · · ) · · · =q_0+q_1x+q_2x^2+q_3x^3+ · · · $

$q_i$表示由a,b,c,d….组成 $i$ 的种类

1
2
3
4
5
6
7
8
9
10
11
12
for (int i=0;i<=n;i++){
ans[i]=1;
tmp[i]=0;
}//element[0]=1时这样写,视情况而定
for (int i=1; i<elememt_count; i++){
for (int j=0;j<=n;j++)
for (int k=0;k+j<=n; k+=element[i]) tmp[k+j]=(tmp[k+j]+ans[j])%mod;
for (int j=0;j<=n;j++){
ans[j]=tmp[j];
tmp[j]=0;
}
}
  • 分治FFT

    计算 $\large\prod(1+a_ix)$ ,复杂度 $\mathcal{O}(n*log^2n)$

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    ll qPow(ll a, ll b, ll c) {  //求(a^b) % c
    ll ret = 1;
    while (b) {
    if (b & 0x1) ret = ret * a % c;
    a = a * a % c;
    b >>= 1;
    }
    return ret;
    }
    typedef complex <db> cp;
    int nn;
    cp A[N],omg[M], inv[M];
    void init(int a,int b){
    nn=1;
    while(nn < a + b) nn <<= 1;
    for(int i = 0; i < nn; i++){
    omg[i] = cp(cos(2 * pi * i / nn), sin(2 * pi * i / nn));
    inv[i] = conj(omg[i]);
    }
    }
    void fft(cp *a,cp *type){//type=omg 系数->点值 ; type=inv 点值->系数
    int lim = 0;
    while((1 << lim) < nn) lim++;
    for(int i = 0; i < nn; i++){
    int t = 0;
    for(int j = 0; j < lim; j++)
    if((i >> j) & 1) t |= (1 << (lim - j - 1));
    if(i < t) swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
    }
    for(int l = 2; l <= nn; l *= 2){
    int m = l / 2;
    for(cp *p = a; p != a + nn; p += l)
    for(int i = 0; i < m; i++){
    cp t = type[nn / l * i] * p[i + m];
    p[i + m] = p[i] - t;
    p[i] += t;
    }
    }
    }
    int a[M];
    int main(){
    int n,trn=1;
    in(n);
    while(trn<n)trn<<=1;
    for0(i,n){
    in(a[i]);
    }
    for0(i,trn){
    A[4*i].real(1);
    A[4*i+1].real(a[i]);
    }
    int len=2;
    for(int i=4;i<4*trn;i<<=1){
    init(len,len);
    int nxt_len=2*len-1;
    for(int j=0;j<4*trn;j+=2*i){
    for(int k=len;k<nn;k++)A[j+k]=A[j+k+i]=cp(0,0);
    fft(A+j,omg);fft(A+j+i,omg);
    for(int k=0;k<nn;k++)A[j+k]*=A[j+k+i];
    fft(A+j,inv);
    for(int k=0;k<nxt_len;k++)A[j+k]=cp(ll(A[j+k].real()/nn+0.1)%mod,0);
    }
    len=nxt_len;
    }
    for(int i=0;i<=n;i++){
    a[i]=A[i].real()+0.1;
    out(a[i],0);
    }
    }

傅立叶变换

DFT

离散傅立叶变换 $O(n^2)$

  • 复数相乘的规则:模长相乘,幅角相加。
  • 取n个复数,单元根$\large \omega_n=\cos\frac{2\pi}n+i\sin\frac{2\pi}n=e^{i\frac {2\pi}n}$,取$\omega_n^0,\omega_n^1,\omega_n^2,\cdots,\omega_n^{n-1}$
    • $\omega_{2n}^{2k}=\omega_{n}^{k}$
    • $\omega_{n}^{k+\frac n2}=-\omega_{n}^{k}$
    • $\omega_n^0=\omega_n^n=1$
  • 设$(y_0,y_1,\cdots,y_{n-1})$为多项式$A(x)=a_0+a_1x+\cdots+a_{n-1}x^{n-1}$的离散傅立叶变换,设多形式$B(x)=y_0+y_1x+\cdots+y_{n-1}x^{n-1}$,取$\omega_n^0,\omega_n^{-1},\omega_n^{-2},\cdots,\omega_n^{-(n-1)}$代入$B(x)$得$(z_0,z_1,\cdots,z_{n-1})$,易证$a_i=\frac {z_i} n$

FFT

快速傅立叶变换,在$O(n log n)$的时间内将一个多项式转换成它的点值表示(或相反)的算法

设$A(x) = a_0 + a_1x + a_2x^2 +…+a_{n-1}x^{n-1}$,将$A(x)$拆分成奇偶两部分:

设:

当$k<\frac n 2$时,

当$>\frac n 2$时,

蝴蝶操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
typedef complex <double> cp;
int n;
cp a[M], b[M], omg[M], inv[M];
void init(int a,int b){
n=1;
while(n < a + b) n <<= 1;
for(int i = 0; i < n; i++){
omg[i] = cp(cos(2 * pi * i / n), sin(2 * pi * i / n));
inv[i] = conj(omg[i]);
}
}
void fft(cp *a, cp *type){//type=omg 系数->点值 ; type=inv 点值->系数
int lim = 0;
while((1 << lim) < n) lim++;
for(int i = 0; i < n; i++){
int t = 0;
for(int j = 0; j < lim; j++)
if((i >> j) & 1) t |= (1 << (lim - j - 1));
if(i < t) swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
}
for(int l = 2; l <= n; l *= 2){
int m = l / 2;
for(cp *p = a; p != a + n; p += l)
for(int i = 0; i < m; i++){
cp t = type[n / l * i] * p[i + m];
p[i + m] = p[i] - t;
p[i] += t;
}
}
}
//2
typedef complex <double> cp;
struct FFT{
int n, R[N];
cp a[N], b[N];
const double pi=acos(-1);
void init(int bound){//bound是积多项式的最高次幂
int L(0);
for(n=1;n<=bound;n<<=1,L++);
for(int i=0;i<n;i++)R[i]=(R[i>>1]>>1)|((i&1)<<(L-1)),a[i]=b[i]=0;
}
void fft(cp* a, int opt){
int i, j, k;
cp wn, w, x, y;
for(i=0;i<n;i++)if(i>R[i])swap(a[i],a[R[i]]);
for(i=1;i<n;i<<=1){
wn=cp(cos(pi/i),opt*sin(pi/i));
for(j=0;j<n;j+=i<<1)
for(w=cp(1,0),k=0;k<i;k++,w=w*wn){
x=a[k+j], y=a[k+j+i]*w;
a[k+j]=x+y, a[k+j+i]=x-y;
}
}
if(opt==-1)for(i=0;i<n;i++)a[i]/=n;
}
void run(){
fft(a,1), fft(b,1);
for(int i=0;i<n;i++)a[i]*=b[i];
fft(a,-1);
}
}fft;

NTT

  • 特定模数

    将单元根$\omega _n=e^{i\frac {2\pi}n} $变为$g^{\frac {P-1} n}$,需要满足模数 P 是质数且 P−1 是 n (2的次幂) 的倍数。即:

    显然对$\omega _n=g^{\frac {P-1} n}$有$\omega_{2n}^{2k}=\omega_{n}^{k}$,而且由于$g^{\frac {P-1} {2n}}\equiv -1 (mod P)$所以$\omega_{n}^{k+\frac n2}=-\omega_{n}^{k}$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int n,r[N];
ll g=3,inv_g,inv_n,mod=998244353;
ll qPow(ll a, ll b, ll c) { //求(a^b) % c
ll ret = 1;
while (b) {
if (b & 0x1) ret = ret * a % c;
a = a * a % c;
b >>= 1;
}
return ret;
}
void init(int a,int b){
int L=0;n=1;
while(n < a + b) n <<= 1,L++;
inv_n = qPow(n,mod-2,mod);
inv_g = qPow(g,mod-2,mod);
for(int i = 0; i < n; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (L - 1));
}
inline void NTT(int *A, int type) {//type=1 系数->点值 ; type=-1 点值->系数
for(int i = 0; i < n; i++) if(i < r[i]) swap(A[i], A[r[i]]);
for(int mid = 1; mid < n; mid <<= 1) {
ll Wn = qPow( type == 1 ? g : inv_g , (mod - 1) / (mid << 1),mod);
for(int j = 0; j < n; j += (mid << 1)) {
ll w = 1;
for(int k = 0; k < mid; k++, w = (w * Wn) % mod) {
int x = A[j + k], y = w * A[j + k + mid] % mod;
A[j + k] = (x + y) % mod,
A[j + k + mid] = (x - y + mod) % mod;
}
}
}
if(type==-1) for0(i,n)A[i]=A[i]*inv_n%mod;
}
int main(){
init(len_a,len_b);
NTT(a, 1); NTT(b, 1);
for(int i=0;i<n;i++) a[i]=1ll*a[i]*b[i]%mod;
NTT(a, -1); //结果放入a中,有效值范围为[0,len_a+len_b-2]
}
  • 三模数

    两个多项式相乘后,每位的答案一定小于1e9*1e9*n,即1e24,所以我们选3个乘积大于1e24的NTT模数,分别做一次,得到每个位上模意义下的答案,再做CRT,先将两个模数意义下的答案合并,得到两个模数,设模数为P1(longlong) ,P2(int), 余数为a1,a2。设答案$ANS=P1\times K+a1=P2\times Q+a2$,那么$K\times P1=P2\times Q+(a2-a1)$,$K\times P1 \% P2=a2-a1$,$a1-a2$为常数,用同余方程的解法即可解出K模P2(int)意义下的值,又有$ANS<P1\times P2$,所以 $K\times P1+a1<P1\times P2$,显然$K<P2$,所以原本答案K的值只能为模P2意义下的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
typedef long long LL;
const int N=262150,mm[3]={7*(1<<26)+1,998244353,479*(1<<21)+1},G=3;
int n,m,kn=1,len,mod;
int a[N],b[N],k1[N],k2[N],ans[3][N],rev[N];
int ksm(int x,int y,int p) {
int re=1;
for(;y;y>>=1,x=1LL*x*x%p) if(y&1) re=1LL*re*x%p;
return re;
}
LL ksc(LL x,LL y,LL p) {return (x*y-(LL)((long double)x/p*y+1e-8)*p+p)%p;}
void NTT(int *a,int n,int p,int x) {
for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
for(RI i=1;i<n;i<<=1) {
int gn=ksm(G,(p-1)/(i<<1),p);
for(RI j=0;j<n;j+=i<<1) {
int g=1,t1,t2;
for(RI k=0;k<i;++k,g=1LL*g*gn%p) {
t1=a[j+k]%p,t2=1LL*g*a[j+i+k]%p;
a[j+k]=(t1+t2)%p,a[j+i+k]=(t1-t2+p)%p;
}
}
}
if(x==1) return;
int inv=ksm(n,p-2,p);reverse(a+1,a+n);
for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv%p;
}
void work(int o) {
for(RI i=0;i<kn;++i) k1[i]=a[i],k2[i]=b[i];
NTT(k1,kn,mm[o],1),NTT(k2,kn,mm[o],1);
for(RI i=0;i<kn;++i) ans[o][i]=1LL*k1[i]*k2[i]%mm[o];
NTT(ans[o],kn,mm[o],-1);
}
int main(){
n=read(),m=read(),mod=read();
for(RI i=0;i<=n;++i) a[i]=read();
for(RI i=0;i<=m;++i) b[i]=read();
while(kn<=n+m) kn<<=1,++len;
for(RI i=1;i<kn;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
for(RI i=0;i<3;++i) work(i);
LL M=1LL*mm[0]*mm[1];
LL kl1=ksc(mm[1],ksm(mm[1]%mm[0],mm[0]-2,mm[0]),M);
LL kl2=ksc(mm[0],ksm(mm[0]%mm[1],mm[1]-2,mm[1]),M);
for(RI i=0;i<=n+m;++i) {
int t0=ksm(ans[0][i],mm[1]-2,mm[1]),t1=ksm(ans[1][i],mm[0]-2,mm[0]);
LL A=(ksc(kl1,ans[0][i],M)+ksc(kl2,ans[1][i],M))%M;
LL k=((ans[2][i]-A)%mm[2]+mm[2])%mm[2]*ksm(M%mm[2],mm[2]-2,mm[2])%mm[2];
printf("%lld ",((M%mod)*(k%mod)%mod+A%mod)%mod);
}
return 0;
}
  • MTT

    将多项式的每个系数拆成$k\times M+b$的形式,其中 M 为常数,考虑卷积过程:

    当$M=\sqrt P$时,这些东西都是 P 的级别,所以FFT 出来的结果都是1e14级别的。所以需要4次 FFT 和3次 IFFT。进一步优化,定义

    则会有

    于是我们只用DFT这个P(x),就能得到Q(x)被DFT之后的结果,还可以求得 A,B 的 FFT。这样我们就可以处理出$a=k_1k_2,b=k_1b_2,c=k_2b_1,d=b_1b_2$的值,然后构造M1=a+bi和M2=c+di两个多项式,IDFT回去。则分别取这两个IDFT后的多项式的实部和虚部(第一个的实部为a,虚部为b以此类推),就是IDFT后的a,b,c,d节约2的常数,这样只要做四次DFT即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
typedef long long LL;
const int N=262150,mm[3]={7*(1<<26)+1,998244353,479*(1<<21)+1},G=3;
int n,m,kn=1,len,mod;
int a[N],b[N],k1[N],k2[N],ans[3][N],rev[N];
int ksm(int x,int y,int p) {
int re=1;
for(;y;y>>=1,x=1LL*x*x%p) if(y&1) re=1LL*re*x%p;
return re;
}
LL ksc(LL x,LL y,LL p) {return (x*y-(LL)((long double)x/p*y+1e-8)*p+p)%p;}
void NTT(int *a,int n,int p,int x) {
for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
for(RI i=1;i<n;i<<=1) {
int gn=ksm(G,(p-1)/(i<<1),p);
for(RI j=0;j<n;j+=i<<1) {
int g=1,t1,t2;
for(RI k=0;k<i;++k,g=1LL*g*gn%p) {
t1=a[j+k]%p,t2=1LL*g*a[j+i+k]%p;
a[j+k]=(t1+t2)%p,a[j+i+k]=(t1-t2+p)%p;
}
}
}
if(x==1) return;
int inv=ksm(n,p-2,p);reverse(a+1,a+n);
for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv%p;
}
void work(int o) {
for(RI i=0;i<kn;++i) k1[i]=a[i],k2[i]=b[i];
NTT(k1,kn,mm[o],1),NTT(k2,kn,mm[o],1);
for(RI i=0;i<kn;++i) ans[o][i]=1LL*k1[i]*k2[i]%mm[o];
NTT(ans[o],kn,mm[o],-1);
}
int main()
{
n=read(),m=read(),mod=read();
for(RI i=0;i<=n;++i) a[i]=read();
for(RI i=0;i<=m;++i) b[i]=read();
while(kn<=n+m) kn<<=1,++len;
for(RI i=1;i<kn;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
for(RI i=0;i<3;++i) work(i);
LL M=1LL*mm[0]*mm[1];
LL kl1=ksc(mm[1],ksm(mm[1]%mm[0],mm[0]-2,mm[0]),M);
LL kl2=ksc(mm[0],ksm(mm[0]%mm[1],mm[1]-2,mm[1]),M);
for(RI i=0;i<=n+m;++i) {
int t0=ksm(ans[0][i],mm[1]-2,mm[1]),t1=ksm(ans[1][i],mm[0]-2,mm[0]);
LL A=(ksc(kl1,ans[0][i],M)+ksc(kl2,ans[1][i],M))%M;
LL k=((ans[2][i]-A)%mm[2]+mm[2])%mm[2]*ksm(M%mm[2],mm[2]-2,mm[2])%mm[2];
printf("%lld ",((M%mod)*(k%mod)%mod+A%mod)%mod);
}
return 0;
}

FWT

快速沃尔什变换,利用类似 $FFT$ 的东西解决多项式的位运算卷积

定义:

寻找 $FWT(A\oplus B)=FWT(A)\times FWT(B)$

  • or

    对于长度为 $2^n$ 的多项式,$A_0$ 是前 $2^{n-1}$ 前项,$A_1$ 是后 $2^{n-1}$ 前项

  • and

    对于长度为 $2^n$ 的多项式,$A_0$ 是前 $2^{n-1}$ 前项,$A_1$ 是后 $2^{n-1}$ 前项

  • xor

    对于长度为 $2^n$ 的多项式,$A_0$ 是前 $2^{n-1}$ 前项,$A_1$ 是后 $2^{n-1}$ 前项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    ll qPow(ll a, ll b, ll c) {  //求(a^b) % c
    ll ret = 1;
    while (b) {
    if (b & 0x1) ret = ret * a % c;
    a = a * a % c;
    b >>= 1;
    }
    return ret;
    }
    int n,inv_2;
    void init(int maxx){//maxx=max(len_a,len_b)
    n=1;
    while(n<maxx)n<<=1;
    inv_2=qPow(2,mod-2,mod);
    }
    void FWT_or(int *a,int opt){//type=1 系数->点值 ; type=-1 点值->系数
    for(int i=1;i<n;i<<=1)
    for(int p=i<<1,j=0;j<n;j+=p)
    for(int k=0;k<i;++k)
    if(opt==1)a[i+j+k]=(a[j+k]+a[i+j+k])%mod;
    else a[i+j+k]=(a[i+j+k]+mod-a[j+k])%mod;
    }
    void FWT_and(int *a,int opt){
    for(int i=1;i<n;i<<=1)
    for(int p=i<<1,j=0;j<n;j+=p)
    for(int k=0;k<i;++k)
    if(opt==1)a[j+k]=(a[j+k]+a[i+j+k])%mod;
    else a[j+k]=(a[j+k]+mod-a[i+j+k])%mod;
    }
    void FWT_xor(int *a,int opt){
    for(int i=1;i<n;i<<=1)
    for(int p=i<<1,j=0;j<n;j+=p)
    for(int k=0;k<i;++k){
    int X=a[j+k],Y=a[i+j+k];
    a[j+k]=(X+Y)%mod;
    a[i+j+k]=(X+mod-Y)%mod;
    if(opt==-1) a[j+k]=1ll*a[j+k]*inv_2%mod , a[i+j+k]=1ll*a[i+j+k]*inv_2%mod;
    }
    }

FMT

快速莫比乌斯变化

阶乘最末尾非零

设 $G(n)$ 为除去 $5$ 的倍数的前 $n$ 项乘积的末尾位

$\frac 2 2=6, \frac 4 2=2, \frac 6 2=8, \frac 8 2=4$

$\huge\frac {G(n)} {2^{\lfloor\frac n 5\rfloor\%4}}$有一个长度为20的循环节6,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2,6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//普通版
int G[10]={4,4,8,4,6,6,6,2,6,4},G5[5]={0,1,2,6,4},ce[4]={6,8,4,2};
int f(int n){
if(n<5)return G5[n];
return G[(n-5)%10]*ce[n/5%4]*f(n/5);
}
int main() {
int n;
ind(n);
printf("%d\n",f(n)%10);
return 0;
}
//高精度
#include <stdio.h>
#include <string.h>
#define MAXN 10000
int lastdigit(char* buf) {
const int mod[20] = {1, 1, 2, 6, 4, 2, 2, 4, 2, 8, 4, 4, 8, 4, 6, 8, 8, 6, 8, 2};
int len = strlen(buf), a[MAXN], i, c, ret = 1;
if (len == 1) return mod[buf[0] - '0'];
for (i = 0; i < len; i++) a[i] = buf[len - 1 - i] - '0';
for (; len; len -= !a[len - 1]) {
ret = ret * mod[a[1] % 2 * 10 + a[0]] % 5;
for (c = 0, i = len - 1; i >= 0; i--)c = c * 10 + a[i], a[i] = c / 5, c %= 5;
}
return ret + ret % 2 * 5;
}
int main() {
char n[10000];
while (scanf("%s", n) != EOF) printf("%d/n", lastdigit(n));
}

二次剩余

当存在某个 $X$,式子 $X^2 = n (\% p)$ 成立时,称 $n$ 是模 $p$ 的二次剩余
当对任意 $X$ 不成立时,称 $n$ 是模 $p$ 的二次非剩余

  • 定理1: 对于 $x^2 = n (\% p)$ 总共有 $\frac {p-1} 2$ 种 n 能使方程有解(排除n=0)

  • 引理: p为奇素数

  • 定理2: 勒让德符号(Legendre symbol)

  • 二次互反律

    设 p,q 是不同的奇素数, 则

  • 广义二次互反律

    设 a,b 为正奇数, 则

  • Cipolla算法

    求解 $x^2 = n (\% p)$
    当 $\omega=a^2-n$ 不是模 p 的二次剩余 $x=(a+\sqrt {\omega})^{\frac {p+1}2}$

    证明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//方案1:
ll sqrtMod(ll a, ll p){ // x^2 == a (mod P)
ll b,k,i;
if(p==2)return a%p;
if(qPow(a,(p-1)/2,p)==1){
if(p%4==3)return qPow(a,(p+1)/4,p);
for(b=1;qPow(b,(p-1)/2,p)==1;b++);
i=(p-1)/2;k=0;
do{
i/=2;
k/=2;
if((qPow(a,i,p)*qPow(b,k,p)+1)%p==0)k+=(p-1)/2;
}while(i%2==0);
return (qPow(a,(i+1)/2,p)*qPow(b,k/2,p))%p;
}
return -1;
}

//方案2:
ll w, P;
struct Complex {
ll r, i;
Complex(ll _r = 0, ll _i = 0):r(_r), i(_i) {}
Complex operator +(ll x) {
return Complex((x + r) % P, i);
}
Complex operator *(Complex a) {
ll rr = (a.r * r % P + w * a.i % P * i % P + P * 3) % P,
ii = (a.i * r % P + a.r * i % P) % P;
return Complex(rr, ii);
}
};
ll complexPow(Complex a, ll b) {
Complex c(1);
while (b) {
if (b & 1) c = c * a;
a = a * a; b >>= 1;
}
return c.r;
}
ll Solve(ll a) { // x^2 == a (mod P)
if (qPow(a, (P - 1) / 2 , P) == P - 1) return -1;
if (qPow(a, (P - 1) / 2 ,P) == 0) return 0;
ll tmp;
Complex k(0, 1);
while (true) {
tmp = rand();
if (qPow((tmp * tmp - a + P) % P, (P - 1) / 2,P) == P - 1) break;
}
w = (tmp * tmp - a + P) % P;
return complexPow(k + tmp, (P + 1) / 2);
}
  • 求解 $x^2 = n (\% p^a)$ , 其中 $gcd(n , p) = 1$, 并且 p 为奇质数

    若 r 是 $x^2 = n (\% p)$ 的一个解

  • 二次同余方程模合数

    求出 $x_1,x_2,\cdots,x_m$ 再用中国剩余定理求解

阶

定义

设 $m>1 , (a,m)=1$,满足 $a^x\equiv 1(\mod m)$ 的最小的 $x$,称为 $a$ 对 $m$ 的阶,记为 $\delta_m(a)$

定理与推论

  1. 若 $m>1,(a,m)=1$ 且 $a^n\equiv 1(\mod m),n>0$ ,则 $\delta_m(a) | n$
  2. $\delta_{m}(a) | \phi(m)$
  3. 若 $p,q$ 为奇素数,且 $q | (a^p-1)$,则或有 $q | (a-1)$,或有 $q=2kp+1$,其中 $k$ 为某整数
  4. $2^p−1$ 的任何因子必取 $2kp+1$ 的形式。
  5. $\large a^{0}, a^{1}, \dots, a^{\delta_m(a)-1} $ 构成模 m 的既约剩余系
  6. $a^{x} \equiv a^{y}(\bmod m) \Leftrightarrow x \equiv y\left(\bmod \delta_{m}(a)\right)$
  7. $\Large \delta_{m}\left(a^{d}\right)=\frac{\delta_{m}(a)}{\left(\delta_{m}(a), d\right)}$

求法

分解 $\Large \phi(m)=\prod p_i^{k_i}$ , 逐渐减小 $k_i$ ,记为 $P$,直到再减一 $[a^P\ne 1(\mod m)]$ 为止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
ll qPow(ll a, ll b, ll c) {  //求(a^b) % c
ll ret = 1;
while (b) {
if (b & 0x1) ret = ret * a % c;
a = a * a % c;
b >>= 1;
}
return ret;
}
ll gcd(ll a,ll b){
return b?gcd(b,a%b):a;
}
int euler(int n){
int m=(int)sqrt(n+0.5);
int ans=n;
for(int i=2;i<=m;i++){
if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
pair<ll,int> factor[50];
int num_factor = 0;
void decomposition(ll n) {
num_factor = 0;
int m=(int)sqrt(n+0.5);
for(int i=2;i<=m;i++){
if(n%i==0){
factor[num_factor]=pair<ll,int>(i,0);
while(n%i==0){
factor[num_factor].se++;
n/=i;
}
num_factor++;
}
}
if (n > 1) factor[num_factor++] = pair<ll,int>(n,1);
}
int order(int a,int m){//求a在模m意义下的阶
if(gcd(a,m)!=1)return 0;
int phi=euler(m);
decomposition(phi);
for0(i,num_factor){
while(qPow(a,phi/factor[i].fi,m)==1&&factor[i].se){
factor[i].se--;
phi/=factor[i].fi;
}
}
return phi;
}

原根

定义

当 $\delta_m(a)=\phi (m)$ 时称为 $a$ 为 $m$ 的原根

定理与推论

  1. 若 $g$ 是 $m$ 的一个原根,则 $g, g^{2}, \cdots, g^{\phi(m)}$ 各数对模 $m$ 的最小剩余,恰是小于 $m$ 且与 $m$ 互素的 $\phi(m)$ 个正整数的一个排列。
  2. $m$ 的原根(若存在)的数目为 $\large\phi(\phi(m))$
  3. $m$ 有原根 $\Longleftrightarrow$ $\large m=1,2,4,p^k,2p^k$ ( $p$ 为奇素数)
  4. 若 $d|(p−1)$ ,则 $x^d\equiv 1(\mod p)$ 恰有 $d$ 个解
  5. 若 $p$ 为素数,$d|(p−1)$,则阶为 $d$ 的最小剩余 $(\mod p)$ 的个数为 $\phi(d)$
  6. 一个数的最小原根为 $O(n^{0.25})$
  7. 若 $g$ 是 $m$ 的原根,则 $g^d$ 为 $m$ 的原根的充要条件为 $(d,\phi(n))=1$

求法

分解 $\Large \phi(m)=\prod p_i^{k_i}$ ,枚举 $g$ ,直到 $\Large g^{\frac {\phi(m)} {p_i}}\ne 1(\mod m)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
ll qPow(ll a, ll b, ll c) {  //求(a^b) % c
ll ret = 1;
while (b) {
if (b & 0x1) ret = ret * a % c;
a = a * a % c;
b >>= 1;
}
return ret;
}
int euler(int n){
int m=(int)sqrt(n+0.5);
int ans=n;
for(int i=2;i<=m;i++){
if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
}
if(n>1) ans=ans/n*(n-1);
return ans;
}
int factor[50];
int num_factor = 0;
void decomposition(ll n) {
num_factor = 0;
int m=(int)sqrt(n+0.5);
for(int i=2;i<=m;i++){
if(n%i==0){
factor[num_factor]=i;
while(n%i==0)n/=i;
num_factor++;
}
}
if (n > 1) factor[num_factor++] =n;
}
int root(int m){
int phi=euler(m);
decomposition(phi);
int g=2;
while(1){
bool yes=1;
for0(i,num_factor){
if(qPow(g,phi/factor[i],m)==1){
yes=0;
break;
}
}
if(yes)break;
g++;
}
return g;
}

离散对数

定义

设 $g$ 是 $m$ 的一个原根,当 $g^t\equiv k(\mod m)$ 时,$in d_gk\equiv t(\mod \phi(m))$ ,此处的 $ind_gk$ 表示 $k$ 以 $g$ 为底,模 $\phi(m)$ 的离散对数值

定理与推论

  1. $ind_gab\equiv ind_ga+ind_gb (\mod \phi(m))$
  2. $ind_ga^n\equiv n\times ind_ga (\mod \phi(m))$

BSGS 算法

$a^x\equiv C(\% m) , gcd(a,m)=1$

$0\le x< \delta_m(a)$, 设 $x=uT-v$ , 则原方程转化为 $\large a^{uT}\equiv Ca^v(a^{-1}存在)$, 我们可以预处理出每一个 $Ca^v$ 对每个 $a^{uT}$ 找一下有没有等于 $Ca^v$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//map
ll BSGS(ll a, ll b, ll p) { // a^x == b (mod p)
if(a%p==0)return -1;
if(b==1)return 0;
map<ll, int> h;
ll m = ceil(sqrt(p)), t = b%p, tt = qPow(a,m,p), v = 1;
for (ll i = 0; i < m; ++i) {
h[t] = i+1;
t = t * a % p;
}
for (ll i = 1; i <= m; ++i) {
v = v * tt % p;
if (h[v]) return i * m - h[v]+1;
}
return -1;
}

//hash_table
struct Hash {
static const int MOD = 2e6+7;
static const int N = 2e6;
ll head[MOD + 10], nx[N], top;
ll hs[N], id[N];
void init() {
memset(head, -1, sizeof head);
top = 0;
}
ll find(ll x) {
ll k = x % MOD;
for (int i = head[k]; i != -1; i = nx[i]) {
if (hs[i] == x) {
return id[i];
}
}
return -1;
}
bool insert(ll x, ll y) {//x-key y-val
if(find(x)!=-1)return 0;
ll k = x % MOD;
hs[top] = x; id[top] = y; nx[top] = head[k]; head[k] = top++;
return 1;
}
}hs;
ll BSGS(ll a, ll b, ll p) { // a^x == b (mod p)
if(a%p==0)return -1;
if(b==1)return 0;
hs.init();
ll m = ceil(sqrt(p)), t = b%p , tt = qPow(a,m,p),v=1;
for (ll i = 0; i < m; ++i) {
hs.insert(t,i);
t = t * a % p;
}
for (ll i = 1; i <= m; ++i) {
v = v * tt % p;
if (hs.find(v) >= 0) return i * m - hs.find(v);
}
return -1;
}

扩展 BSGS

初始方程为

设 $b’=b,p’=p$,初始化 $v= 1$,每次约分拿出一个 $a$ ,令

如果
$d=1$,则约分过程结束,约分完成。否则的话,如果 $d$ 不能整除
$b’$,说明无法完成约分,$x$ 无解,整个算法结束。
再否则的话,更新
$b’,p’,v$ 为

然后继续循环进行下一轮约分。
其中 $v$ 即为 $a$ 每次约分剩下的没有被约掉的因子的乘积。约分完成后,假设约分了 $cnt$ 次,则程变为

此时
$(a,p’) = 1$,显然我们可用基本的 $BGGS$ 算法求得 $x -cnt$ 的值,算法至此结束。
具体可参考下方的实现理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ll exBSGS(ll a, ll b, ll p) {
a %= p;b %= p;
if (a == b) return 1;
if(b==1)return 0;
if(a==0)return -1;
ll ret = 1;
for (ll i = 0; i <= 50; ++i) {
if (ret == b) return i;
ret = (ret * a) % p;
} //枚举比较小的i
ll x, y, d, v = 1, cnt = 0;
while ((d = gcd(a, p)) != 1) {
if (b % d) return -1;
b /= d, p /= d;
v = (v * (a / d)) % p;
++cnt;
} //约分直到(a, p) == 1
if(b==1)return cnt;
init();
ll m = ceil(sqrt(p)), t = b , tt = qPow(a,m,p);
for (ll i = 0; i < m; ++i) {
Insert(i, t);
t = t * a % p;
}
for (ll i = 1; i <= m; ++i) {
v = v * tt % p;
if (Find(v) >= 0) return i * m - Find(v)+cnt;
}
return -1;
}

全错位排列

其他

康托展开

计算当前排列在所有由小到大全排列中的顺序
$X = A[0] \times (n-1)! + A[1] \times (n-2)! + … + A[n-1] \times 0! $

  • $A[i]$ 指的是位于位置 $i$ 后面的数小于 $A[i]$ 值的个数,后面乘的就是后面还有多少个数的阶乘

  • 说明 :这个算出来的数康拖展开值,是在所有排列次序 $- 1$的值,因此 $X+1$ 即为在全排列中的次序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //返回数组a中当下顺序的康拖映射
    int cantor(int *a,int n)
    {
    int ans=0;
    for(int i=0;i<n;i++)
    {
    int x=0;int c=1,m=1;//c记录后面的阶乘
    for(int j=i+1;j<n;j++)
    {
    if(a[j]<a[i])x++;
    m*=c;c++;
    }
    ans+=x*m;
    }
    return ans;
    }

逆康拖展开
例子 :
在(1,2,3,4,5) 给出61可以算出起排列组合为34152
具体过程如下:
用 61 / 4! = 2余13,说明 ,说明比首位小的数有2个,所以首位为3。
用 13 / 3! = 2余1,说明 ,说明在第二位之后小于第二位的数有2个,所以第二位为4。
用 1 / 2! = 0余1,说明 ,说明在第三位之后没有小于第三位的数,所以第三位为1。
用 1 / 1! = 1余0,说明 ,说明在第二位之后小于第四位的数有1个,所以第四位为5。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static const int FAC[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};   // 阶乘

//康托展开逆运算
void decantor(int x, int n){
vector<int> v; // 存放当前可选数
vector<int> a; // 所求排列组合
for(int i=1;i<=n;i++)v.push_back(i);
for(int i=m;i>=1;i--){
int r = x % FAC[i-1];
int t = x / FAC[i-1];
x = r;
sort(v.begin(),v.end());// 从小到大排序
a.push_back(v[t]); // 剩余数里第t+1个数为当前位
v.erase(v.begin()+t); // 移除选做当前位的数
}
}
高斯整数

高斯整数是实数和虚数部分都是整数的复数。所有高斯整数组成了一个整域,写作 $Z[i]$, 是个不可以转成有序环的欧几里德域。$Z[i] = \{a+bi | a,b \in \mathbb Z \}$

$Z[i]$ 的单位元是 $1,-1,i,-i$

对 $Z[sqrt(-k)], k \in \{1,2,3,7,11,19,43,67,163\}$ 才满足唯一分解定理

高斯素数

  • $a ,b$ 中有一个是 $0$,另一个是形如 $4n+3$ 或 $-(4n+3)$ 的素数
  • $a,b$ 均不为 $0$,$a^2+b^2$ 为素数
四平方和定理

每个正整数均可表示为4个整数的平方和

费马平方和定理

奇素数能表示为两个平方数之和的充分必要条件是该素数被4除余1。

如果两个整数都能表示为两个平方数之和,则它们的积也能表示为两个平方数之和。

如果一个能表示为两个平方数之和的整数被另一个能表示为两个平方数之和的素数整除,则它们的商也能表示为两个平方数之和。

如果一个能表示为两个平方数之和的整数被另一个不能表示为两个平方数之和的整数整除,则它们的商也必有一个不能表示为两个平方数之和的因子。

如果 $a$ 和 $b$ 互素,则 $a^2+b^2$ 的所有因子都能表示为两个平方数之和。

阶乘近似
N以内相邻素数最大间距

$ln N \times (ln N - 2\times ln ln N)+2$

调和级数
快速平方根倒数

$\frac 1 {\sqrt x}$

1
2
3
4
5
6
7
8
float fast_inverse_sqrt(float x) {
float half_x = 0.5 * x;
int i = *((int *)&x); // 以整数方式读取X
i = 0x5f3759df - (i >> 1); // 神奇的步骤
x = *((float *)&i); // 再以浮点方式读取i
x = x * (1.5 - (half_x * x * x)); // 牛顿迭代一遍提高精度
return x;
}
N 次剩余

$x^a\equiv b(\% p)$

  • $p$ 为素数

    设 $g$ 为 $P$ 的一原根,记 $k$ 关于 $g$ 的离散对数 $(mod p)$ 为 $ind_dk$,则有

    设
    $t_1=ind_gb$,则有
    $g^{t_1}\equiv b (mod p)$ 。那么 $t_1$ 即与情形2—致,可由 $BSGS$ 算法求出
    $t_1$。

    设
    $t_2=ind_gx$,则原式变为
    $a\times t_2\equiv t_1 (mod \phi (p))$
    其中只有
    $t_2$ 是未知的,可由扩展欧几里德求出所有 $t_2$
    的值。
    又由
    $t_2=ind_gx$
    知 $g^{t_2}\equiv x (mod p)$ 与情形1 一致,可由快速幂求出所有 $x$ 值。
    至此解决 $P$ 为素数时的问题。

  • p为合数

    首先将 $p$ 分解素因子为
    $p=p_1^{e_1}p_2^{e_2}\cdots p_k^{e_k}$

    那么我们对每一个 $i$ 属于 $[1,\cdots,k]$,都有
    $x^a \equiv b (mod p_i^{e_i})$ 我们可利用上面的知识去解出这里的 $x$,不过由于模数不一定为素数的原因,不能单纯地使用上面 $p$ 为素数的方法去解,关于这个式子的解法是解决整个问题的关键,下面会给出细节,此处暂且认为已经解出 $x$ 的一
    组解 $x_{i1},x_{i2},\cdots,x_{it_i}$。

    那么上面 $k$ 个式子就会有 $k$ 组这样的解,我们从每组里取一个 $x$ 值出来,记为 $\{X_1,X_1,\cdots,X_k\}$,那么如果我们能找到一个 $z$ 满足

    那么 $z$ 必为原式的一个解。而要解这个,只需使用 $CRT$ 即可,同时我们需要一个 $dfs$ 来枚举所有 $X$ 可能的组合。
    至此解决问题。

大素数判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
1. a^(p-1) ≡ 1(mod p)
2. 二次探次定理 若a^2≡1(mod p),则a≡±1(mod p)
3. Miller-Rabin质数测试 p-1=d*2^r a^d mod n=1或者存在a^(d*2^i) mod n=n-1 ( 0<=i<r )
如果n<2^64,只用选取a=2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37做测试即可
以a为基,n-1=x*2^t a^(n-1)=1(mod n) 验证n是不是合数
*/
bool check(ll a,ll n,ll x,ll t){//一定是合数返回true,不一定返回false
ll res = qPow(a,x,n);
ll last = res;
for(int i = 1; i <= t; ++i){
res=qMulti(res,res,n);
if(res==1 && last !=1 && last != n-1)return true;//合数
last = res;
}
if(res != 1)return true;
return false;
}
bool Miller_Rabin(ll n){
if(n < 2) return false;
if(n == 2) return true;
if((n&1) == 0) return false;//偶数
ll x = n-1,t = 0;
while((x&1)==0){
x >>= 1;
++t;
}
for(int i = 0; i < 20; ++i){
ll a = rand()%(n-1)+1;
if(check(a,n,x,t))return false;//合数
}
return true;
}

//2
bool isPrime(ll n){
ll d,u,t,p[]={2,3,5,7,11,13,17,19,23,29,31,37};
for(d=n-1 ; !(d&1) ; )d>>=1;
for(int i=0;i<12;i++){
if(!(n%p[i]))return n==p[i];
for(t=qPow(p[i],u=d,n);t!=n-1&&t!=1&&u!=n-1;)t=qMulti(t,t,n),u<<=1;
if(t!=n-1&&!(u&1))retrun 0;
}
retrun 1;
}
Pollard-Rho 质因数分解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
ll factor[100];//质因数分解结果(刚返回时是无序的)
int tol;//质因数的个数。数组小标从0开始
ll gcd(ll a,ll b){
if(a==0)return 1;//???????
if(a<0) return gcd(-a,b);
while(b){
ll t=a%b;
a=b;
b=t;
}
return a;
}
ll Pollard_rho(ll x,ll c){
ll i=1,k=2,x0=rand()%x, y=x0;
while(1){
i++;
x0=(qMulti(x0,x0,x)+c)%x;
ll d=gcd(y-x0,x);
if(d!=1&&d!=x) return d;
if(y==x0) return x;
if(i==k){
y=x0;
k+=k;
}
}
}
void findfac(ll n){ //对n进行素因子分解
if(Miller_Rabin(n)){
factor[tol++]=n;
return;
}
ll p=n;
while(p>=n)p=Pollard_rho(p,rand()%(n-1)+1);
findfac(p);
findfac(n/p);
}

Others

Posted on 2019-08-17 | Edited on 2019-09-26 | In 模板 | Views:

Others

binary search

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//求最小的i,使得a[i] = key,若不存在,则返回-1
int Equal_Min(int a[], int n, int key) {
int m, l = 0, r = n - 1; //闭区间[0, n - 1]
while (l < r) {
m = l + ((r - l) >> 1);
if (a[m] >= key)r = m - 1;
else l = m + 1;
}
return (m < n) && (a[m] == key) ? m : -1;
}
//求最大的i,使得a[i] = key,若不存在,则返回-1
int Equal_Max(int a[], int n, int key) {
int m, l = 0, r = n - 1; //闭区间[0, n - 1]
while (l < r) {
m = l + ((r - l) >> 1);
if (a[m] > key)r = m - 1;
else l = m + 1;
}
return (l - 1 >= 0 && (a[l - 1] == key)) ? l - 1 : -1;
}
//求最小的i,使得a[i] > key,若不存在,则返回-1
int Grater(int a[], int n, int key) {
int m, l = 0, r = n - 1; //闭区间[0, n - 1]
while (l <= r) {
m = l + ((r - l) >> 1);
if (a[m] > key) r = m - 1;
else l = m + 1;
}
return l < n ? l : -1;
}
//求最小的i,使得a[i] >= key,若不存在,则返回-1
int Grater_Equal(int a[], int n, int key) {
int m, l = 0, r = n - 1; //闭区间[0, n - 1]
while (l <= r) {
m = l + ((r - l) >> 1);
if (a[m] >= key) r = m - 1;
else l = m + 1;
}
return l < n ? l : -1;
}
//求最大的i,使得a[i] < key,若不存在,则返回-1
int Smaller(int a[], int n, int key) {
int m, l = 0, r = n - 1; //闭区间[0, n - 1]
while (l <= r) {
m = l + ((r - l) >> 1);
if (a[m] >= key) r = m - 1;
else l = m + 1;
}
return l > 0 ? l - 1 : -1;
}
//求最大的i,使得a[i] <= key,若不存在,则返回-1
int Equal_Smaller(int a[], int n, int key) {
int m, l = 0, r = n - 1; //闭区间[0, n - 1]
while (l <= r) {
m = l + ((r - l) >> 1);
if (a[m] > key) r = m - 1;
else l = m + 1;
}
return l > 0 ? l - 1 : -1;
}

蔡勒公式

求星期

1
2
3
4
int week(int y,int m,int d){
if(m==1||m==2) m+=12,y=y-1;
return (d+2*m+3*(m+1)/5+y+y/4-y/100+y/400+1)%7;
}

DLX算法

20150123215406542

  • 精准覆盖问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    int a[N];
    struct node{
    int l,r,u,d,col,row;
    };
    struct DLX{
    node p[M];
    int len;
    int size[N],last[N];
    inline void make(int l,int r,int u,int d,int col,int row){len++;p[len].l=l;p[len].r=r;p[len].u=u;p[len].d=d;p[len].col=col;p[len].row=row;}
    inline void ud_del(int x){p[p[x].u].d=p[x].d;p[p[x].d].u=p[x].u;}
    inline void ud_res(int x){p[p[x].u].d=p[p[x].d].u=x;}
    inline void lr_del(int x){p[p[x].l].r=p[x].r;p[p[x].r].l=p[x].l;}
    inline void lr_res(int x){p[p[x].l].r=p[p[x].r].l=x;}
    inline void init(int m){//m - number of column
    len=0;
    p[0].l=p[0].r=p[0].u=p[0].d=0;
    for(int i=1;i<=m;i++){
    size[i]=0;
    last[i]=i;
    make(i-1,p[i-1].r,i,i,i,0);
    lr_res(i);
    }
    }
    inline void add(int row){
    if(a[0]==0)return;
    make(len+1,len+1,last[a[1]],p[last[a[1]]].d,a[1],row);
    ud_res(len);
    size[a[1]]++;
    last[a[1]]=len;
    for(int i=2;i<=a[0];i++){
    make(len,p[len].r,last[a[i]],p[last[a[i]]].d,a[i],row);
    ud_res(len);
    lr_res(len);
    size[a[i]]++;
    last[a[i]]=len;
    }
    }
    inline void del(int x){
    lr_del(x);
    for(int i=p[x].u;i!=x;i=p[i].u){
    for(int j=p[i].l;j!=i;j=p[j].l)ud_del(j),size[p[j].col]--;
    }
    }
    inline void res(int x){
    lr_res(x);
    for(int i=p[x].u;i!=x;i=p[i].u){
    for(int j=p[i].l;j!=i;j=p[j].l)ud_res(j),size[p[j].col]++;
    }
    }
    int dance(int dep){
    if(!p[0].r)return dep;
    int c,mi=inf;
    for(int i=p[0].r;i;i=p[i].r){
    if(size[p[i].col]<mi)mi=size[p[i].col],c=i;
    }
    if(mi==0)return 0;
    del(c);
    for(int i=p[c].u;i!=c;i=p[i].u){
    for(int j=p[i].l;j!=i;j=p[j].l)del(p[j].col);
    int tt=dance(dep+1);
    if(tt)return tt;
    for(int j=p[i].r;j!=i;j=p[j].r)res(p[j].col);
    }
    res(c);
    return 0;
    }
    }dlx;
  • 最小重复覆盖

    允许列重复,求满足覆盖的最小行数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    int a[N];
    struct node{
    int l,r,u,d,col,row;
    };
    int ans;
    struct DLX{
    node p[M];
    int len;
    int size[N],last[N];
    inline void make(int l,int r,int u,int d,int col,int row){len++;p[len].l=l;p[len].r=r;p[len].u=u;p[len].d=d;p[len].col=col;p[len].row=row;}
    inline void ud_del(int x){p[p[x].u].d=p[x].d;p[p[x].d].u=p[x].u;}
    inline void ud_res(int x){p[p[x].u].d=p[p[x].d].u=x;}
    inline void lr_del(int x){p[p[x].l].r=p[x].r;p[p[x].r].l=p[x].l;}
    inline void lr_res(int x){p[p[x].l].r=p[p[x].r].l=x;}
    inline void init(int x){
    len=0;
    p[0].l=p[0].r=p[0].u=p[0].d=0;
    for(int i=1;i<=x;i++){
    size[i]=0;last[i]=i;
    make(i-1,p[i-1].r,i,i,i,0);
    lr_res(i);
    }
    }
    inline void add(int row){
    if(a[0]==0)return;
    make(len+1,len+1,last[a[1]],p[last[a[1]]].d,a[1],row);
    ud_res(len);
    size[a[1]]++;
    last[a[1]]=len;
    for(int i=2;i<=a[0];i++){
    make(len,p[len].r,last[a[i]],p[last[a[i]]].d,a[i],row);
    ud_res(len);
    lr_res(len);
    size[a[i]]++;
    last[a[i]]=len;
    }
    }
    inline void del(int x){
    for(int i=p[x].u;i!=x;i=p[i].u){
    lr_del(i);
    }
    }
    inline void res(int x){
    for(int i=p[x].u;i!=x;i=p[i].u){
    lr_res(i);
    }
    }
    bool vis[N];
    int ex(){//使用 A* 的期望函数来剪枝
    int cnt=0;
    mem0(vis);
    for(int i=p[0].r;i;i=p[i].r){
    if(vis[i])continue;
    cnt++;
    vis[i]=1;
    for(int j=p[i].u;j!=i;j=p[j].u){
    for(int k=p[j].l;k!=j;k=p[k].l)vis[p[k].col]=1;
    }
    }
    return cnt;
    }
    void dance(int dep){
    if(dep+ex()>=ans)return;
    if(!p[0].r){
    ans=min(ans,dep);
    return;
    }
    int first,mi=inf;
    for(int i=p[0].r;i;i=p[i].r){
    if(size[p[i].col]<mi)mi=size[p[i].col],first=i;
    }
    if(mi==0)return;
    for(int i=p[first].u;i!=first;i=p[i].u){
    del(i);
    for(int j=p[i].l;j!=i;j=p[j].l)del(j);
    dance(x+1);
    for(int j=p[i].r;j!=i;j=p[j].r)res(j);
    res(i);
    }
    return ;
    }
    }dlx;

读入挂

  • 整数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    inline void read(ll &x) {
    ll f=1;
    x=0;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    x*=f;
    }
    inline void write(ll x) {
    if (x < 0) {
    putchar('-');
    x = -x;
    }
    if (x >= 10)write(x / 10);
    putchar(x % 10 + '0');
    }
  • 实数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    inline bool scan_lf(double &num) {
    char in;
    double Dec = 0.1;
    bool IsN = false, IsD = false;
    in = getchar();
    if (in == EOF) return false;
    while (in != '-' && in != '.' && (in < '0' || in > '9')) in = getchar();
    if (in == '-') {
    IsN = true;
    num = 0;
    } else if (in == '.') {
    IsD = true;
    num = 0;
    } else num = in - '0';
    if (!IsD) {
    while (in = getchar(), in >= '0' && in <= '9') {
    num *= 10;
    num += in - '0';
    }
    }
    if (in != '.') {
    if (IsN) num = -num;
    return true;
    } else {
    while (in = getchar(), in >= '0' && in <= '9') {
    num += Dec * (in - '0');
    Dec *= 0.1;
    }
    }
    if (IsN) num = -num;
    return true;
    }

分数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
ll gcd(ll a,ll b){
return b?gcd(b,a%b):abs(a);
}
struct fraction{
ll a, b;
fraction(){}
fraction(ll _a,ll _b){
ll gc=gcd(_a,_b);
a=_a/gc,b=_b/gc;
if(b<0)b=-b,a=-a;
}
bool operator==(const fraction &x)const{
return a==x.a&&b==x.b;
}
bool operator!=(const fraction &x)const{
return !(a==x.a&&b==x.b);
}
bool operator<(const fraction &y)const{
return a * y.b < y.a * b;
}
bool operator<=(const fraction &y)const{
return a * y.b <= y.a * b;
}
fraction operator*(const fraction &y)const{
return fraction(a*y.a,b*y.b);
}
fraction operator+(const fraction &y)const{
return fraction(a * y.b + y.a * b,b*y.b);
}
fraction operator/(const fraction &y)const{
return fraction(a*y.b,b*y.a);
}
fraction operator-(const fraction &y)const{
return fraction(a * y.b - y.a * b,b*y.b);
}
void display(){
if(b==1)printf("%lld\n",a);
else printf("%lld/%lld\n",a,b);
}
};

归并排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void merge_core(int arr[], int begin, int mid, int end){//将两个有序数组合并成一个
int i=begin, j=mid, k=0;
int *tmp = (int*)malloc(sizeof(int)*(end-begin));
for(; i<mid && j<end; tmp[k++]=(arr[i]<arr[j]?arr[i++]:arr[j++]));
for(; i<mid; tmp[k++]=arr[i++]);
for(; j<end; tmp[k++]=arr[j++]);
for(i=begin, k=0; i<end; arr[i++]=tmp[k++]);
free(tmp);
}
void merge_sort(int arr[], int begin, int end){
if (end-begin < 2) return;
int mid = (begin+end)>>1;
merge_sort(arr,begin,mid);
merge_sort(arr,mid,end);
merge_core(arr,begin,mid,end);
}

利用归并排序求逆序对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int tmpA[100005], cnt = 0;
void merge_sort(int l, int r, int *A) {
if (l >= r) return ;
int mid = (l + r) >> 1;
merge_sort(l, mid, A);
merge_sort(mid + 1, r, A);
int pl = l, pr = mid + 1, tmpp = 0;
while(pl <= mid && pr <= r) {
if (A[pl] <= A[pr]) tmpA[tmpp++] = A[pl++];
else tmpA[tmpp++] = A[pr++], cnt += mid - pl + 1;
}
while(pl <= mid) tmpA[tmpp++] = A[pl++];
while(pr <= r) tmpA[tmpp++] = A[pr++];
for (int i = 0; i < tmpp; i++) A[i + l] = tmpA[i];
}

hash_table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const int seed = N;
int tot, First[seed], Next[N], Val[N], Key[N];
void init() {
tot = 0;
mem0(First);
}
void Insert(int key, int val) {
int u = val % seed;
for (int v = First[u]; v; v = Next[v])
if (Val[v] == val && Key[v] < key) {
Key[v] = key;
return;
}
Next[++tot] = First[u];
First[u] = tot;
Val[tot] = val;
Key[tot] = key;
}
int Find(int val) {
int u = val % seed;
for (int v = First[u]; v; v = Next[v])if (Val[v] == val) return Key[v];
return -1;
}

unsigned int ELFhash(char *str) {
unsigned int hash = 0, x = 0;
while (*str) {
hash = (hash << 4) + *str;
if ((x = hash & 0xf0000000) != 0) {
hash ^= (x >> 24);
hash &= ~x;
}
str++;
}
return (hash & 0x7fffffff);
}

unsigned int ELFhash(string str) {
unsigned int hash = 0,x = 0;
for0(i, str.size()) {
hash = (hash << 4) + str[i];
if ((x = hash & 0xf0000000) != 0) {
hash ^= (x >> 24);
hash &= ~x;
}
}
return (hash & 0x7fffffff);
}

快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void quick_sort(int l,int r,int m[]){
int i=l,j=r,temp=m[l];
if (l>r) return;
while (i!=j) {
while (m[j]>=temp&&i<j) j--;
while (m[i]<=temp&&i<j) i++;
if (i<j){
int k=m[j];
m[j]=m[i];
m[i]=k;
}
}
m[l]=m[i];
m[i]=temp;
quick_sort(l,i-1,m);
quick_sort(i+1,r,m);
}

曼哈顿距离和切比雪夫距离

曼哈顿距离

$\large dis_m=|x1-x2|+|y1-y2|$

切比雪夫距离

$\large dis_q=max(|x1-x2|,|y1-y2|)$

转换

  • 将坐标 $(x,y)\to(x+y,x-y)$ ,原坐标的 $dis_m$ 等于新坐标的 $dis_q$
  • 将坐标 $(x,y)\to(\frac {x+y}2,\frac {x-y}2)$ ,原坐标的 $dis_q$ 等于新坐标的 $dis_m$

位运算

  • 按位与运算符(&)

    • 运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;

    • x & (x - 1)
      1.用于消去x最后一位的1
      2.检查n是否为2的幂次
      3.计算在一个 32 位的整数的二进制表式中有多少个 1
      4.如果要将整数A转换为B,需要改变多少个bit位:对A^B进行操作

    • Lowbit(x)

      1
      2
      x&(-x) 
      x&(x^(x-1))
    • 枚举所有集合的子集

      1
      2
      3
      4
      5
      for (int S=1,len=1<<n; S<len; S++){
      for (int T=(S-1)&S; T ; T=(T-1)&S){ //枚举S的所有非空真子集
      //do something.
      }
      }
  • 按位或运算符(|)

    • 运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1
  • 异或运算符($\text{^}$)

    • 运算规则:$\text{0^0=0; 0^1=1; 1^0=1; 1^1=0;}$
    • 性质:
      1.交换律
      2.结合律 $\text{即(a^b)^c == a^(b^c)}$
      3.对于任何数 x,都有 $\text{x^x=0,x^0=x}$
      4.自反性: $\text{a^b^b=a^0=a;}$
    • $\text{a ^ b ^ b = a}$
      数组中,只有一个数出现一次,剩下都出现两次,找出出现一次的数

线性递推式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#define SZ(x) ((ll)(x).size())
typedef vector<ll> VI;
typedef pair<ll,ll> PII;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}

namespace linear_seq {
const long long N = 10010;
ll res[N], base[N], _c[N], _md[N];

VI Md;
void mul(ll *a, ll *b, long long k) {
for0(i, k + k) _c[i] = 0;
for0(i, k) if (a[i]) for0(j, k) _c[i + j] =
(_c[i + j] + a[i] * b[j]) % mod;
for (long long i = k + k - 1; i >= k; i--)
if (_c[i])
for0(j, SZ(Md)) _c[i - k + Md[j]] =
(_c[i - k + Md[j]] - _c[i] * _md[Md[j]]) % mod;
for0(i, k) a[i] = _c[i];
}
long long solve(ll n, VI a, VI b) { // a 系数 b 初值 b[n+1]=a[0]*b[n]+...
ll ans = 0, pnt = 0;
long long k = SZ(a);
assert(SZ(a) == SZ(b));
for0(i, k) _md[k - 1 - i] = -a[i];
_md[k] = 1;
Md.clear();
for0(i, k) if (_md[i] != 0) Md.push_back(i);
for0(i, k) res[i] = base[i] = 0;
res[0] = 1;
while ((1ll << pnt) <= n) pnt++;
for (long long p = pnt; p >= 0; p--) {
mul(res, res, k);
if ((n >> p) & 1) {
for (long long i = k - 1; i >= 0; i--) res[i + 1] = res[i];
res[0] = 0;
for0(j, SZ(Md)) res[Md[j]] =
(res[Md[j]] - res[k] * _md[Md[j]]) % mod;
}
}
for0(i, k) ans = (ans + res[i] * b[i]) % mod;
if (ans < 0) ans += mod;
return ans;
}
VI BM(VI s) {
VI C(1, 1), B(1, 1);
long long L = 0, m = 1, b = 1;
for0(n, SZ(s)) {
ll d = 0;
for0(i, L + 1) d = (d + (ll)C[i] * s[n - i]) % mod;
if (d == 0)
++m;
else if (2 * L <= n) {
VI T = C;
ll c = mod - d * powmod(b, mod - 2) % mod;
while (SZ(C) < SZ(B) + m) C.pb(0);
for0(i, SZ(B)) C[i + m] = (C[i + m] + c * B[i]) % mod;
L = n + 1 - L;
B = T;
b = d;
m = 1;
} else {
ll c = mod - d * powmod(b, mod - 2) % mod;
while (SZ(C) < SZ(B) + m) C.pb(0);
for0(i, SZ(B)) C[i + m] = (C[i + m] + c * B[i]) % mod;
++m;
}
}
return C;
}
ll gao(VI a, ll n) {
VI c = BM(a);
c.erase(c.begin());
for0(i, SZ(c)) c[i] = (mod - c[i]) % mod;
return solve(n, c, VI(a.begin(), a.begin() + SZ(c)));
}
}; // namespace linear_seq

//2
struct LinearRecurrence {
using vec = vector<LL>;

static void extand(vec &a, LL d, LL value = 0) {
if (d <= a.size()) return;
a.resize(d, value);
}

static vec BerlekampMassey(const vec &s, LL mod) {
std::function<LL(LL)> inverse = [&](LL a) {
return a == 1 ? 1 : (LL) (mod - mod / a) * inverse(mod % a) % mod;
};
vec A = {1}, B = {1};
LL b = s[0];
for (size_t i = 1, m = 1; i < s.size(); ++i, m++) {
LL d = 0;
for (size_t j = 0; j < A.size(); ++j) {
d += A[j] * s[i - j] % mod;
}
if (!(d %= mod)) continue;
if (2 * (A.size() - 1) <= i) {
auto temp = A;
extand(A, B.size() + m);
LL coef = d * inverse(b) % mod;
for (size_t j = 0; j < B.size(); ++j) {
A[j + m] -= coef * B[j] % mod;
if (A[j + m] < 0) A[j + m] += mod;
}
B = temp, b = d, m = 0;
} else {
extand(A, B.size() + m);
LL coef = d * inverse(b) % mod;
for (size_t j = 0; j < B.size(); ++j) {
A[j + m] -= coef * B[j] % mod;
if (A[j + m] < 0) A[j + m] += mod;
}
}
}
return A;
}

static void exgcd(LL a, LL b, LL &g, LL &x, LL &y) {
if (!b) x = 1, y = 0, g = a;
else {
exgcd(b, a % b, g, y, x);
y -= x * (a / b);
}
}

static LL crt(const vec &c, const vec &m) {
LL n = c.size();
LL M = 1, ans = 0;
for (LL i = 0; i < n; ++i) M *= m[i];
for (LL i = 0; i < n; ++i) {
LL x, y, g, tm = M / m[i];
exgcd(tm, m[i], g, x, y);
ans = (ans + tm * x * c[i] % M) % M;
}
return (ans + M) % M;
}

static vec ReedsSloane(const vec &s, LL mod) {
auto inverse = [](LL a, LL m) {
LL d, x, y;
exgcd(a, m, d, x, y);
return d == 1 ? (x % m + m) % m : -1;
};
auto L = [](const vec &a, const vec &b) {
LL da = (a.size() > 1 || (a.size() == 1 && a[0])) ? (LL) a.size() - 1 : -1000;
LL db = (b.size() > 1 || (b.size() == 1 && b[0])) ? (LL) b.size() - 1 : -1000;
return max(da, db + 1);
};
auto prime_power = [&](const vec &s, LL mod, LL p, LL e) {
// linear feedback shift register mod p^e, p is prime
vector<vec> a(e), b(e), an(e), bn(e), ao(e), bo(e);
vec t(e), u(e), r(e), to(e, 1), uo(e), pw(e + 1);
pw[0] = 1;
for (LL i = pw[0] = 1; i <= e; ++i) pw[i] = pw[i - 1] * p;
for (LL i = 0; i < e; ++i) {
a[i] = {pw[i]}, an[i] = {pw[i]};
b[i] = {0}, bn[i] = {s[0] * pw[i] % mod};
t[i] = s[0] * pw[i] % mod;
if (t[i] == 0) {
t[i] = 1, u[i] = e;
} else {
for (u[i] = 0; t[i] % p == 0; t[i] /= p, ++u[i]);
}
}
for (LL k = 1; k < s.size(); ++k) {
for (LL g = 0; g < e; ++g) {
if (L(an[g], bn[g]) > L(a[g], b[g])) {
ao[g] = a[e - 1 - u[g]];
bo[g] = b[e - 1 - u[g]];
to[g] = t[e - 1 - u[g]];
uo[g] = u[e - 1 - u[g]];
r[g] = k - 1;
}
}
a = an, b = bn;
for (LL o = 0; o < e; ++o) {
LL d = 0;
for (LL i = 0; i < a[o].size() && i <= k; ++i) {
d = (d + a[o][i] * s[k - i]) % mod;
}
if (d == 0) {
t[o] = 1, u[o] = e;
} else {
for (u[o] = 0, t[o] = d; t[o] % p == 0; t[o] /= p, ++u[o]);
LL g = e - 1 - u[o];
if (L(a[g], b[g]) == 0) {
extand(bn[o], k + 1);
bn[o][k] = (bn[o][k] + d) % mod;
} else {
LL coef = t[o] * inverse(to[g], mod) % mod * pw[u[o] - uo[g]] % mod;
LL m = k - r[g];
extand(an[o], ao[g].size() + m);
extand(bn[o], bo[g].size() + m);
for (LL i = 0; i < ao[g].size(); ++i) {
an[o][i + m] -= coef * ao[g][i] % mod;
if (an[o][i + m] < 0) an[o][i + m] += mod;
}
while (an[o].size() && an[o].back() == 0) an[o].pop_back();
for (LL i = 0; i < bo[g].size(); ++i) {
bn[o][i + m] -= coef * bo[g][i] % mod;
if (bn[o][i + m] < 0) bn[o][i + m] -= mod;
}
while (bn[o].size() && bn[o].back() == 0) bn[o].pop_back();
}
}
}
}
return make_pair(an[0], bn[0]);
};

vector<tuple<LL, LL, LL>> fac;
for (LL i = 2; i * i <= mod; ++i)
if (mod % i == 0) {
LL cnt = 0, pw = 1;
while (mod % i == 0) mod /= i, ++cnt, pw *= i;
fac.emplace_back(pw, i, cnt);
}
if (mod > 1) fac.emplace_back(mod, mod, 1);
vector<vec> as;
LL n = 0;
for (auto &&x: fac) {
LL mod, p, e;
vec a, b;
tie(mod, p, e) = x;
auto ss = s;
for (auto &&x: ss) x %= mod;
tie(a, b) = prime_power(ss, mod, p, e);
as.emplace_back(a);
n = max(n, (LL) a.size());
}
vec a(n), c(as.size()), m(as.size());

for (LL i = 0; i < n; ++i) {
for (LL j = 0; j < as.size(); ++j) {
m[j] = get<0>(fac[j]);
c[j] = i < as[j].size() ? as[j][i] : 0;
}
a[i] = crt(c, m);
}
return a;
}

LinearRecurrence(const vec &s, const vec &c, LL mod) :
init(s), trans(c), mod(mod), m(s.size()) {}

LinearRecurrence(const vec &s, LL mod, bool is_prime = true) : mod(mod) {
vec A;
if (is_prime) A = BerlekampMassey(s, mod);
else A = ReedsSloane(s, mod);
if (A.empty()) A = {0};
m = A.size() - 1;
trans.resize(m);
for (LL i = 0; i < m; ++i) {
trans[i] = (mod - A[i + 1]) % mod;
}
reverse(trans.begin(), trans.end());
init = {s.begin(), s.begin() + m};
}

LL calc(LL n) {
if (mod == 1) return 0;
if (n < m) return init[n];
vec v(m), u(m << 1);

LL msk = !!n;
for (LL m = n; m > 1; m >>= 1LL) msk <<= 1LL;
v[0] = 1 % mod;
for (LL x = 0; msk; msk >>= 1LL, x <<= 1LL) {
fill_n(u.begin(), m * 2, 0);
x |= !!(n & msk);
if (x < m) u[x] = 1 % mod;
else { // can be optimized by fft/ntt
for (LL i = 0; i < m; ++i) {
for (LL j = 0, t = i + (x & 1); j < m; ++j, ++t) {
u[t] = (u[t] + v[i] * v[j]) % mod;
}
}
for (LL i = m * 2 - 1; i >= m; --i) {
for (LL j = 0, t = i - m; j < m; ++j, ++t) {
u[t] = (u[t] + trans[j] * u[i]) % mod;
}
}
}
v = {u.begin(), u.begin() + m};
}
LL ret = 0;
for (LL i = 0; i < m; ++i) {
ret = (ret + v[i] * init[i]) % mod;
}
return ret;
}

vec init, trans;
LL mod;
LL m;
};

String

Posted on 2019-08-17 | Edited on 2019-09-21 | In 模板 | Views:

String

最长公共子串

$dp[i][j]$表示前 $i$ 个 $a$ 和前 $j$ 个 $b$ 组成的最长公共子串长度

最长公共子序列

$dp[i][j]$表示的前 $i$ 个 $a$ 和前 $j$ 个 $b$ 组成最长公共子序列长度

最长上升公共子序列

$dp[i][j]$ 表示前 $i$ 个 $a$ 和前 $j$ 个 $b$ 组成的以 $b[j]$ 结尾的最长上升子公共子序列长度

最长回文子序列

最长回文子串

  • Manacher 算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
char s[N];
int Manacher(char *s, int len) {
char Ma[2 * N + 10];
int l = 0, ans = 0, Mp[2 * N + 10];
Ma[l++] = '$';
Ma[l++] = '#';
for (int i = 0; i < len; i++) {
Ma[l++] = s[i];
Ma[l++] = '#';
}
Ma[l] = 0;
int mx = 0, id = 0;
for (int i = 1; i < l; i++) {
Mp[i] = mx > i ? min(Mp[2 * id - i], mx - i) : 1;
while (Ma[i + Mp[i]] == Ma[i - Mp[i]]) Mp[i]++;
if (i + Mp[i] > mx) {
mx = i + Mp[i];
id = i;
}
}
for0(i, 2 * len + 2) ans = max(ans, Mp[i] - 1);
return ans;
}

int Manacher(char *s, int len) {//s 从1开始
int ans = 0, r[N];
int R = 0, id = 0;
for (int i = 1; i <=len; i++) {
r[i] = R>i ? min(r[2 * id - i], R - i):0; //r[i] 表示以 i 为中心的回文串半径(不包括 i )
while (i+r[i]+1<=len&&s[i - r[i] - 1]==s[i + r[i] + 1] ) r[i]++;
if (i + r[i] > R) {
R = i + r[i];
id = i;
}
ans=max(ans,2*r[i]+1);
}
id=R=0;
for(int i=2;i<=len;i++){
r[i] = R>i ? min(r[2 * id - i], R - i + 1):0;//r[i] 表示以 i-1 和 i 为中心的回文串半径
while (i+r[i]<=len&&s[i - r[i] - 1]==s[i + r[i]]){
r[i]++;
}
if (i + r[i]-1 > R) {
R = i + r[i]-1;
id = i;
}
ans=max(ans,2*r[i]);
}
return ans;
}

回文自动机

功能:

  • 求串 S 前缀 0~i 内本质不同回文串的个数(两个串长度不同或者长度相同且至少有一个字符不同便是本质不同)
  • 求串 S 内每一个本质不同回文串出现的次数
  • 求串 S 内回文串的个数(其实就是1和2结合起来)
  • 求以下标 i 结尾的回文串的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const int C = 26;
struct Palindromic_Tree {
int next[N][C] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[N] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[N] ;//cnt[i]表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[N] ;//num[i]表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数。
int len[N] ;//len[i]表示节点i表示的回文串的长度
int S[N] ;//存放添加的字符
int last ;//指向上一个字符所在的节点,方便下一次add
int n ;//字符数组指针
int p ;//节点指针

int newnode ( int l ) {//新建节点
for ( int i = 0 ; i < C ; ++ i ) next[p][i] = 0 ;
cnt[p] = 0 ;
num[p] = 0 ;
len[p] = l ;
return p ++ ;
}

void init () {//初始化
p = 0 ;
newnode ( 0 ) ;
newnode ( -1 ) ;
last = 0 ;
n = 0 ;
S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
fail[0] = 1 ;
}

int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的
while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
return x ;
}

void add ( int c ) {
c -= 'a' ;
S[++ n] = c ;
int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
int now = newnode ( len[cur] + 2 ) ;//新建节点
fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
next[cur][c] = now ;
num[now] = num[fail[now]] + 1 ;
}
last = next[cur][c] ;
cnt[last] ++ ;
}

void count () {
for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
//父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!
}
} PAM;

字符串哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ull P = 1313131;
ull sqr[N/2],has[N/2],V[N];
int Laxt[N],Next[N],cnt=0;
const int MOD = 2e6+7;

bool _insert(ull Now){
int u=Now%MOD;
for(int i=Laxt[u];i;i=Next[i]){
if(V[i]==Now) return 0;
}
Next[++cnt]=Laxt[u];
Laxt[u]=cnt;
V[cnt]=Now;
return 1;
}
int ans=0;
void _hash(int x,int y){
ull Now=has[y]-has[x-1]*sqr[y-x+1];//求s[x]···s[y]的hash值
if(_insert(Now)) ++ans;
}
void init(char *s,int Len){//s 从1开始
sqr[0]=1;
for(int i=1;i<=Len;i++){
sqr[i]=sqr[i-1]*P;
has[i]=has[i-1]*P+s[i];
}
}

序列自动机

$nxt[i][j]$ 表示在原串 $s$ (从 $1$ 开始) 第 $i$ 位后面的第一个 $j$ 出现的位置,设串长为 $n$ ,字符集大小为 $a$,预处理时间复杂度为 $O(n*a)$

1
2
3
4
for(int i=n;i;i--){
for(int j=0;j<26;j++) nxt[i-1][j]=nxt[i][j];
nxt[i-1][s[i]-'a']=i;
}

求子序列个数

1
2
3
4
5
6
7
int dfs(int x){
if(f[x]) return f[x];
for(r int i=1;i<=a;i++)
if(next[x][i]) f[x]+=dfs(next[x][i]);
return ++f[x];
}
//调用:dfs(0);

求回文子序列或两个串的公共子序列个数等

1
2
3
4
5
6
7
int dfs(int x,int y){//表示目前字符是串1的第x位,串2的第y位
if(f[x][y]) return f[x][y];
for(r int i=1;i<=a;i++)
if(next1[x][i]&&next2[y][i]) f[x][y]+=dfs(next1[x][i],next2[y][i]);
return ++f[x][y];
}
//调用:dfs(0,0);

求一个串的回文子序列个数

对原串和反串构造 $next$ 数组, 然后 $dfs$ , 对回文分奇偶, $dfs$ 过程中应该保证 $x+y<=n+1$ , 若 $x+y=n+1$ 为奇串, 否则为偶串. $dfs $ 到一个偶串时,可能会有奇串被漏掉(如自动机只能找到 $abba$,却忽略了 $aba$) 因此,我们考虑手动加上被忽略的奇串,同时注意已经找到的奇串不能再加一次(如 $ababa$),只要特判一下就可以了,代码如下。

1
2
3
4
5
6
7
8
9
10
11
int dfs(int x,int y){
if(f[x][y]) return f[x][y];//记忆化
for(int i=1;i<=26;i++)
if(next1[x][i]&&next2[y][i]){
if(next1[x][i]+next2[y][i]>n+1) continue;//x+y>n+1,状态不合法,不进行统计
if(next1[x][i]+next2[y][i]<n+1) f[x][y]++;
//满足x+y=n+1的奇串不会被漏掉,其他奇串需要特别统计
f[x][y]=(f[x][y]+dfs(next1[x][i],next2[y][i]))%mod;
}
return ++f[x][y];
}

给定三个串 $A,B.C$, 求一个最长的 $A,B$ 的公共子序列 $S$, 要求 $C$ 是 $S$ 的子序列

再加一个参数 $z$ 表示当前字符是 $C$ 的第 $z$ 位, 但这么做显然是错的,因为匹配过程中 $C$ 的某些字符可能会被跳过, 所以需要对 $next$ 做一些修改

1
2
3
4
5
for(int i=1;i<=26;i++) nextc[n][i]=n;//字符集为26个字母,C长度为n
for(int i=0;i<n;i++){
for(r int j=1;j<=26;j++) nextc[i][j]=i;
nextc[i][c[i+1]]=i+1;
}

KMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int Next[N];// Next[i] 为满足 p[i-z,...,i-1]=p[0,...,z-1] 的最大 z 值(就是 p 的自身匹配)
void kmp_pre(char *p, int p_len,int Next[]) {
int i = 0, j = Next[0] = -1;
while (i < p_len) {
while (j != -1 && p[i] != p[j]) j = Next[j];
Next[++i] = ++j;
}
}
int KMP(char *p, char *s) { // p 是模式串,s 是主串
int ans = 0, i = 0, j = 0, p_len = strlen(p), s_len = strlen(s);
kmp_pre(p, p_len, Next);
while (i < s_len) {
while (-1 != j && s[i] != p[j]) j = Next[j];
i++;
j++;
if (j >= p_len) {
ans++;
j = Next[j];
}
}
return ans;
}

扩展KMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int Extend[N], Next[N];
void GetNext(string S) { // Next[i] 为满足 S[i,...,i+z-1]=S[0,...,z-1] 的最大 z 值,即S[i]...S[s_len-1]与S的最长公共前缀长度
int s_len = (int)S.size(), a = 0, p = 0;
Next[0] = s_len;
for (int i = 1, j = -1; i < s_len; i++, j--) {
if (j < 0 || i + Next[i - a] >= p) {
if (j < 0) {
p = i;
j = 0;
}
while (p < s_len && S[p] == S[j]) {
p++;
j++;
}
Next[i] = j;
a = i;
} else Next[i] = Next[i - a];
}
}
void exKMP(string P, string S) { // P[i]...P[p_len-1]与S的最长公共前缀的长度
GetNext(S);
int a = 0, p = 0, p_len = (int)P.size(), s_len = (int)S.size(); //记录匹配成功的字符的最远位置p,及起始位置a
for (int i = 0, j = -1; i < p_len; i++,j--) { // j即等于p与i的距离,其作用是判断i是否大于p(如果j<0,则i大于p)
if (j < 0 || i + Next[i - a] >= p) {
if (j < 0) {
p = i;
j = 0;
}
while (p < p_len && j < s_len && P[p] == S[j]) {
p++;
j++;
}
Extend[i] = j;
a = i;
} else Extend[i] = Next[i - a];
}
}

字典树

  • 数组实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    int ch[N][30], tot, val[N];
    void Insert(char *s) {
    int root = 0, len = (int)strlen(s);
    for0(i, len) {
    int c = s[i] - 'a';
    if (!ch[root][c]) {
    mem0(ch[tot + 1]); //使用的时候才用memset开辟空间可以减低空间复杂度
    ch[root][c] = ++tot;
    }
    root = ch[root][c];
    val[root]++;
    }
    }
    int Query(char *s) {
    int root = 0;
    int len = (int)strlen(s);
    for0(i, len) {
    int c = s[i] - 'a';
    if (!ch[root][c]) return 0;
    root = ch[root][c];
    }
    return val[root];
    }
  • 指针实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    const int MAX = 26;
    struct Trie {
    Trie *Next[MAX];
    int count;
    Trie() {
    count = 1;
    memset(Next, NULL, sizeof(Next));
    }
    } * root;
    void Insert(string s) {
    Trie *tmpRoot = root, *tmp;
    for0(i, s.size()) {
    if (tmpRoot->Next[s[i] - 'a'] == NULL) {
    tmp = new Trie;
    tmpRoot->Next[s[i] - 'a'] = tmp;
    tmpRoot = tmp;
    } else {
    tmpRoot = tmpRoot->Next[s[i] - 'a'];
    tmpRoot->count++;
    }
    }
    }
    int Query(string s) {
    Trie *tmpRoot = root;
    for0(i, s.size()) {
    if (tmpRoot->Next[s[i] - 'a'] == NULL) return 0;
    tmpRoot = tmpRoot->Next[s[i] - 'a'];
    }
    return tmpRoot->count;
    }
    void Free(Trie *T) {
    if (T == NULL) return;
    for (int i = 0; i < MAX; ++i) {
    if (T->Next[i]) Free(T->Next[i]);
    }
    delete (T);
    }

AC自动机

多模匹配算法,多个模式串,在文本串中找出拥有的模式串数,求 $fail$ : 直接与根节点相连的节点来说,如果这些节点失配,他们的Fail指针直接指向root即可;假设当前节点为father,其孩子节点记为child。求child的Fail
指针时,首先我们要找到其father的Fail指针所指向的节点,假如是t的话,我们就要看t的孩子中有没有和child节点所表示的字母相同的节点,如果有的话,
这个节点就是child的fail指针,如果发现没有,则需要找father->fail->fail这个节点,然后重复上面过程,如果一直找都找不到,则child的Fail指针就要指向root。

  • 数组实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    struct Trie {
    int next[N][26], fail[N], end[N], root, L; // N的范围为所有pattern的字符数
    int newnode() {
    for (int i = 0; i < 26; i++) next[L][i] = -1;
    end[L++] = 0;
    return L - 1;
    }
    void init() {
    L = 0;
    root = newnode();
    }
    void insert(char buf[]) {
    int len = (int)strlen(buf), now = root;
    for (int i = 0; i < len; i++) {
    if (next[now][buf[i] - 'a'] == -1)next[now][buf[i] - 'a'] = newnode();
    now = next[now][buf[i] - 'a'];
    }
    end[now]++;
    }
    void build() {
    queue<int> Q;
    fail[root] = root;
    for (int i = 0; i < 26; i++) {
    if (next[root][i] == -1)next[root][i] = root;
    else {
    fail[next[root][i]] = root;
    Q.push(next[root][i]);
    }
    }
    while (!Q.empty()) {
    int now = Q.front();
    Q.pop();
    for (int i = 0; i < 26; i++) {
    if (next[now][i] == -1)next[now][i] = next[fail[now]][i];
    else {
    fail[next[now][i]] = next[fail[now]][i];
    Q.push(next[now][i]);
    }
    }
    }
    }
    int query(char buf[]) {
    int len = (int)strlen(buf), now = root, res = 0;
    for (int i = 0; i < len; i++) {
    now = next[now][buf[i] - 'a'];
    int temp = now;
    while (temp != root) {
    res += end[temp];
    end[temp] = 0;
    temp = fail[temp];
    }
    }
    return res;
    }
    };
    char *pattern, *str;
  • 指针实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    const int MAX = 26;
    int tot;
    vii failTree[N];
    struct Node {
    int id;
    Node *Next[MAX];
    Node *Fail; //失配指针,类似next数组,最大的next是和自身比较,fail是和其它匹配串比较
    Node() {
    memset(Next, 0, sizeof(Next));
    Fail = 0;
    id=tot++;
    }
    } * root;
    int Insert(char s[]) {
    Node *p = root;
    int len=strlen(s);
    for(int i=0;i< len;i++) {
    int c = s[i] - 'a';
    if (p->Next[c] == NULL) {
    Node *newnode = new Node;
    p->Next[c] = newnode;
    p = newnode;
    } else p = p->Next[c];
    }
    return p->id;
    }
    void BuildFail() {
    queue<Node *> que;
    root->Fail=root;
    for(int i=0;i<26;i++){
    if(root->Next[i]){
    root->Next[i]->Fail=root;
    que.push(root->Next[i]);
    }else root->Next[i]=root;
    }
    while (!que.empty()) {
    Node *p = que.front();
    que.pop();
    failTree[p->Fail->id].pu_b(p->id);
    for(int i=0;i<26;i++){
    if(p->Next[i]){
    p->Next[i]->Fail=p->Fail->Next[i];
    que.push(p->Next[i]);
    }else p->Next[i]=p->Fail->Next[i];
    }
    }
    }

后缀数组

倍增算法 $O(n*logn)$

  • 待排序数组长度为n, 放在[0,n-1]中,在最后面补一个0,需要保证每个元素大于零
  • srank[0,n-1]为有效值,srank[n]必定为0无效值,srank[i]保存Suffix(i)在所有后缀中从小到大排列的“名次”。
  • sa[1,n]为有效值,sa[0] 必定为n是无效值, sa[i]表示排名第i的起点为sa[i];
  • height[2,n]为有效值,suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    int t1[N],t2[N],c[N],srank[N],height[N],sa[N];
    bool cmp(int *r,int a,int b,int l){
    return r[a] == r[b] && r[a+l] == r[b+l];
    }
    void DA(int str[],int n,int m){//m为字符串中的最大值+1
    n++;
    int i, j, p, *x = t1, *y = t2;
    //第一轮基数排序,如果s 的最大值很大,可改为快速排序
    for(i = 0;i < m;i++)c[i] = 0;
    for(i = 0;i < n;i++)c[x[i] = str[i]]++;
    for(i = 1;i < m;i++)c[i] += c[i-1];
    for(i = n-1;i >= 0;i--)sa[--c[x[i]]] = i;
    for(j = 1;j <= n; j <<= 1){
    p = 0;
    //直接利用sa 数组排序第二关键字
    for(i = n-j; i < n; i++)y[p++] = i;//后面的j 个数第二关键字为空的最小
    for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;//这样数组y保存的就是按照第二关键字排序的结果
    //基数排序第一关键字
    for(i = 0; i < m; i++)c[i] = 0;
    for(i = 0; i < n; i++)c[x[y[i]]]++;
    for(i = 1; i < m;i++)c[i] += c[i-1];
    for(i = n-1; i >= 0;i--)sa[--c[x[y[i]]]] = y[i];//根据sa 和x 数组计算新的x 数组
    swap(x,y);
    p = 1; x[sa[0]] = 0;
    for(i = 1;i < n;i++)x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    if(p >= n)break;
    m = p;//下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i = 0;i <= n;i++)srank[sa[i]] = i;
    for(i = 0;i < n;i++){
    if(k)k--;
    j = sa[srank[i]-1];
    while(str[i+k] == str[j+k])k++;
    height[srank[i]] = k;
    }
    }
    int r[N];

DC3算法 $O(n)$

  • 所有的相关数组都要开三倍
  • 待排序数组长度为n, 放在[0,n-1]中,在最后面补一个0
  • srank[0,n-1]为有效值,srank[n]必定为0无效值,srank[i]保存Suffix(i)在所有后缀中从小到大排列的“名次”
  • sa[1,n]为有效值,sa[0] 必定为n是无效值, sa[i]表示排名第i的起点为sa[i];
  • height[2,n]为有效值,suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    #define F(x) ((x)/3+((x)%3==1?0:tb))
    #define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
    int wa[N],wb[N],wv[N],wss[N],sa[N],srank[N],height[N];
    int c0(int *r,int a,int b){
    return r[a] == r[b] && r[a+1] == r[b+1] && r[a+2] == r[b+2];
    }
    int c12(int k,int *r,int a,int b){
    if(k == 2) return r[a] < r[b] || ( r[a] == r[b] && c12(1,r,a+1,b+1) );
    else return r[a] < r[b] || ( r[a] == r[b] && wv[a+1] < wv[b+1]);
    }
    void sort(int *r,int *a,int *b,int n,int m){
    int i;
    for(i = 0;i < n;i++)wv[i] = r[a[i]];
    for(i = 0;i < m;i++)wss[i] = 0;
    for(i = 0;i < n;i++)wss[wv[i]]++;
    for(i = 1;i < m;i++)wss[i]+=wss[i-1];
    for(i = n-1;i >= 0;i--)b[--wss[wv[i]]] = a[i];
    }
    void dc3(int *r,int *sa,int n,int m){
    int i, j, *rn = r + n;
    int *san = sa + n, ta = 0, tb = (n+1)/3, tbc = 0, p;
    r[n] = r[n+1] = 0;
    for(i = 0;i < n;i++)if(i %3 != 0)wa[tbc++] = i;
    sort(r + 2, wa, wb, tbc, m);
    sort(r + 1, wb, wa, tbc, m);
    sort(r, wa, wb, tbc, m);
    for(p = 1, rn[F(wb[0])] = 0, i = 1;i < tbc;i++)rn[F(wb[i])] = c0(r, wb[i-1], wb[i]) ? p - 1 : p++;
    if(p < tbc)dc3(rn,san,tbc,p);
    else for(i = 0;i < tbc;i++)san[rn[i]] = i;
    for(i = 0;i < tbc;i++) if(san[i] < tb)wb[ta++] = san[i] * 3;
    if(n % 3 == 1)wb[ta++] = n - 1;
    sort(r, wb, wa, ta, m);
    for(i = 0;i < tbc;i++)wv[wb[i] = G(san[i])] = i;
    for(i = 0, j = 0, p = 0;i < ta && j < tbc;p++)sa[p] = c12(wb[j] % 3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
    for(;i < ta;p++)sa[p] = wa[i++];
    for(;j < tbc;p++)sa[p] = wb[j++];
    }
    void DA(int str[],int n,int m){
    for(int i = n;i < n*3;i++)str[i] = 0;
    dc3(str, sa, n+1, m);
    int i,j,k = 0;
    for(i = 0;i <= n;i++)srank[sa[i]] = i;
    for(i = 0;i < n; i++){
    if(k) k--;
    j = sa[srank[i]-1];
    while(str[i+k] == str[j+k]) k++;
    height[srank[i]] = k;
    }
    }
    string s;
    int r[N];

应用

  • 本质不同子串

后缀自动机

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const int CHAR = 26;
struct SAM_Node {
SAM_Node *fa, *next[CHAR];
int len;
long long cnt;
void clear() {
fa = 0;
memset(next, 0, sizeof(next));
cnt = 0;
}
} pool[N * 2];
SAM_Node *root, *tail;
SAM_Node* newnode(int len) {
SAM_Node* cur = tail++;
cur->clear();
cur->len = len;
return cur;
}
void SAM_init() {
tail = pool;
root = newnode(0);
}
SAM_Node* extend(SAM_Node* last, int x) {
SAM_Node *p = last, *np = newnode(p->len + 1);
while (p && !p->next[x]) p->next[x] = np, p = p->fa;
if (!p)
np->fa = root;
else {
SAM_Node* q = p->next[x];
if (q->len == p->len + 1)
np->fa = q;
else {
SAM_Node* nq = newnode(p->len + 1);
memcpy(nq->next, q->next, sizeof(q->next));
nq->fa = q->fa;
q->fa = np->fa = nq;
while (p && p->next[x] == q) p->next[x] = nq, p = p->fa;
}
}
return np;
}

//2
struct NODE{
int ch[26];
int len,fa;
void newNode(int _len){memset(ch,0,sizeof(ch));len=_len;}
}pool[N<<1];
int las=1,tot=1;
void init(){
las=tot=1;
memset(pool[1].ch,0,sizeof(pool[1].ch));
pool[1].len=0;
}
void add(int c){
int p=las,np=las=++tot;
pool[np].newNode(pool[p].len+1);
for(;p&&!pool[p].ch[c];p=pool[p].fa)pool[p].ch[c]=np;
if(!p)pool[np].fa=1;//以上为case 1
else{
int q=pool[p].ch[c];
if(pool[q].len==pool[p].len+1)pool[np].fa=q;//以上为case 2
else{
int nq=++tot;
pool[nq]=pool[q];
pool[nq].len=pool[p].len+1;
pool[q].fa=pool[np].fa=nq;
for(;p&&pool[p].ch[c]==q;p=pool[p].fa) pool[p].ch[c]=nq;//以上为case 3
}
}
}

广义后缀自动机

广义后缀自动机就是把所有串放到一个后缀自动机里一起处理。
每次往后缀自动机里添加一个字符串后,将 $last$ 重新挪到 $1$ 号节点上,再添加下一个字符串即可。添加字符串 $S_i$ 时,添加出来的所有实节点及它们在 $pre$ 树上的祖先们代表的子串,都在字符串 $S_i$ 中出现过。于是我们可以利用一些类似于 $set$ 启发式合并的方法,来得到每个节点在多少个字符串中出现过。

应用

  • 本质不同回文串

  • manacher & hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
int t1[N],t2[N],c[N],srank[N],height[N],sa[N];
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void DA(int str[],int n,int m){//m为字符串中的最大值+1
n++;
int i, j, p, *x = t1, *y = t2;
//第一轮基数排序,如果s 的最大值很大,可改为快速排序
for(i = 0;i < m;i++)c[i] = 0;
for(i = 0;i < n;i++)c[x[i] = str[i]]++;
for(i = 1;i < m;i++)c[i] += c[i-1];
for(i = n-1;i >= 0;i--)sa[--c[x[i]]] = i;
for(j = 1;j <= n; j <<= 1){
p = 0;
//直接利用sa 数组排序第二关键字
for(i = n-j; i < n; i++)y[p++] = i;//后面的j 个数第二关键字为空的最小
for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i = 0; i < m; i++)c[i] = 0;
for(i = 0; i < n; i++)c[x[y[i]]]++;
for(i = 1; i < m;i++)c[i] += c[i-1];
for(i = n-1; i >= 0;i--)sa[--c[x[y[i]]]] = y[i];//根据sa 和x 数组计算新的x 数组
swap(x,y);
p = 1; x[sa[0]] = 0;
for(i = 1;i < n;i++)x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p >= n)break;
m = p;//下次基数排序的最大值
}
int k = 0;
n--;
for(i = 0;i <= n;i++)srank[sa[i]] = i;
for(i = 0;i < n;i++){
if(k)k--;
j = sa[srank[i]-1];
while(str[i+k] == str[j+k])k++;
height[srank[i]] = k;
}
}
int r[N];

int ans[M];
ull P = 1313131;
ull sqr[M],has[M];
unordered_set<ull>ss;
vector<pii> v;
ull getHash(int x,int y){
return has[y]-has[x-1]*sqr[y-x+1];
}


void init(char *s,int Len){//s 从1开始
sqr[0]=1;
for(int i=1;i<=Len;i++){
sqr[i]=sqr[i-1]*P;
has[i]=has[i-1]*P+s[i];
}
}
char s[M];

int dp[M][30];
void ST(int n) {
for (int i = 1; i <= n; i++) dp[i][0] = height[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
dp[i][j] = min(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
}
int RMQ(int l, int r) {
int k = 0;
while ((1 << (k + 1)) <= r - l + 1) k++;
return min(dp[l][k], dp[r - (1 << k) + 1][k]);
}
int len;
void query(int x,int y){
// printf("%d %d\n",x,y);
pii tmp(x,y);
int dep=srank[tmp.fi];
int l=2,r=dep,mid,tans=1;
if(l<=dep){
while(l<=r){
mid=(l+r)/2;
if(RMQ(mid,dep)<tmp.se)l=mid+1;
else r=mid-1;
}
tans+=dep-l+1;
}
l=dep+1,r=len;
while(l<=r){
mid=(l+r)/2;
if(RMQ(dep+1,mid)>=tmp.se)l=mid+1;
else r=mid-1;
}
tans+=r-dep;
ans[tmp.se]+=tans;
}

int insert(int x,int y){
if(!ss.count(getHash(x,y))){
ss.insert(getHash(x,y));
if((y-x+1)%2){
if(getHash(x,(x+y)/2)==getHash((x+y)/2,y))query(x-1,y-x+1);
}else{
if(getHash(x,(x+y)/2)==getHash((x+y)/2+1,y))query(x-1,y-x+1);
}
}
return 0;
}
void Manacher(char *s, int len) {
int r[M];
int R = 0, id = 0;
for (int i = 1; i <=len; i++) {
r[i] = R>i ? min(r[2 * id - i], R - i):insert(i,i); //r[i] 表示以 i 为中心的回文串半径(不包括 i )
while (i+r[i]+1<=len&&s[i - r[i] - 1]==s[i + r[i] + 1] ){
insert(i - r[i] - 1,i + r[i] + 1);
r[i]++;
}
if (i + r[i] > R) {
R = i + r[i];
id = i;
}
}
id=R=0;
for(int i=2;i<=len;i++){
r[i] = R>i ? min(r[2 * id - i], R - i + 1):0;//r[i] 表示以 i-1 和 i 为中心的回文串半径
while (i+r[i]<=len&&s[i - r[i] - 1]==s[i + r[i]]){
insert(i - r[i] - 1,i + r[i]);
r[i]++;
}
if (i + r[i]-1 > R) {
R = i + r[i]-1;
id = i;
}
}
}
int main() {
whiel(~in(s+1)){
len=strlen(s+1);
init(s,len);
for1(i,len){
r[i-1]=s[i]-'a'+1;
}
r[len]=0;
DA(r,len,30);
ST(len);
Manacher(s,len);
for1(i,len){
out(ans[i]);
ans[i]=0;
}
ss.clear();
puts("");
}
cerr<<clock()<<endl;
return 0;
}

ex模板

Posted on 2019-08-17 | Edited on 2019-10-02 | In 模板 | Views:

线性基

线性基是一个数的集合,并且每个序列都拥有至少一个线性基。

性质

  1. 原序列里面任意一个(一些数异或和)都可以由线性基里面的一些数异或得到,同时,线性基里面的任意一个数也可以由原序列里面的一些数异或得到。
  2. 线性基里面的任意一些数异或起来都不能得到 $0$
  3. 线性基里面的数的个数唯一,并且在保持性质一的前提下,数的个数是最少的

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
ll d[100];//线性基,若 d[i]>0,d[i]二进制的第i位一定为1
bool add(ll x){//添加元素
for(int i=63;i>=0;i--){
if(x&(1ll<<i)){
if(d[i])x^=d[i];
else{
d[i]=x;
return 1;
}
}
}
return 0;
}

最大最小

1
2
3
4
5
6
7
8
9
10
11
12
ll MAX(){//求在原序列中,取若干个数,使得其异或和最大
ll ans=0;
for(int i=63;i>=0;i--)
if((ans^d[i])>ans)ans^=d[i];
return ans;
}
ll MIN(){//求在原序列中,取若干个数,使得其异或和最小
for(int i=0;i<64;i++){
if(d[i])return d[i];
}
return 0;
}

第k大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int cnt;
ll p[100];
void work(){//预处理,使得若 d[i]>0,其他d[j]二进制的第i位一定为i,不会影响线性基的正确性
for(int i=1;i<=63;i++)
for(int j=0;j<i;j++)
if(d[i]&(1ll<<j))d[i]^=d[j];
cnt=0;
for(int i=0;i<=63;i++)
if(d[i])p[cnt++]=d[i];
}
ll k_th(ll k){//将k先转成二进制,假如k的第i位为1,ans就异或上线性基中第i个元素(注意不是直接异或d[i-1]
if(k>=(1ll<<cnt))return -1;
work();
ll ans=0;
for(int i=0;i<cnt;i++)
if(k&(1ll<<i))ans^=p[i];
return ans;
}

ex题解

Posted on 2019-08-17 | Edited on 2019-10-02 | In 模板 | Views:

CodeForces - 1216F

题意

$n$ 个房间拍成一排,标号为 $1\sim n$ ,要联网的花费为 $i$ ,一些房间可以装 $Wi-Fi$ ,使得 $[i-k,i+k]$ 的房间都有 $Wi-Fi$ ,问最小花费。

题解

$dp[i]$ 表示前 $i$ 个房间通网的最小花费,初始化为 $\large dp[i]=\frac {(n+1)*n}2$ ,

区间最小值使用树状数组维护

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
char s[N];
ll dp[N],h[N];
int lowbit(int x){
return x&(-x);
}
int n;
void update(int p,ll x){
dp[p]=x;
while(p<=n){
h[p]=min(h[p],x);
p+=p&-p;
}
}
ll query(int l,int r){
if(l==0)return 0;
ll ans = 1e18;
while(l<=r){
ans = min(ans,dp[r]);
r--;
for(; l<=r-lowbit(r) ;r-=lowbit(r))ans = min(ans,h[r]);
}
return ans;
}
void change(int id,ll v){
if(v<dp[id]){
update(id,v);
}
}
int main() {
int k;
memset(h,inf,sizeof(h));
in(n,k);
in(s+1);
for (int i = 1; i <= n; i++)update(i,dp[i-1]+i);
for (int i = 1; i <= n; i++){
change(i,dp[i-1]+i);
if(s[i]=='1'){
int id=min(n,i+k);
change(id,query(max(i-k-1,0),id)+i);
// change(id,query(i,id)+i);
}
}
out(dp[n],1);
return 0;
}
## 51nod-2571 楼梯数字 ### 题意 问有多少长度为 $n(1\le n\le 1e18)$ 的数字(无前导零),满足后一位都不小于前一位,且是 $k(2\le k\le 500)$ 的倍数。 ### 题解 显然这样的阶梯数字是由 $1,11,111,1111,\cdots$ 构成的,我们将这 $n$ 种数按照 $\mod k$ 分类,用 $cnt[i]$ 计数, $dp[i][j]$ 表示使用 $i$ 个数字,其和 $\mod k=j$ 的方案数 ### 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
ll cnt[600],dp[11][600];
ll inv[20],fac[20];
ll qPow(ll a,ll b,ll c){
ll ans=1;
a%=c;
while(b){
if(b%2)ans=ans*a%c;
a=a*a%c;
b/=2;
}
return ans;
}
void init(){//快速计算阶乘的逆元
fac[0]=fac[1]=1;
for(int i=2;i<N;i++)fac[i]=fac[i-1]*i%mod;
inv[N-1] = qPow(fac[N-1], mod - 2, mod);
for (int i = N - 2; i >= 0; i--) inv[i] = (inv[i + 1] * (i + 1)) % mod;
}
ll C(ll n,ll m){
if(n<m)return 0;
ll ans=1;
for(ll i=n-m+1;i<=n;i++){
ans=i%mod*ans%mod;
}
return ans*inv[m]%mod;
}
int main() {
init();
ll n,k;
in(n,k);
vii v;
map<int,int>ma;
v.pu_b(0);
int pre=1;
int l,r;//记录循环节
for(int i=1;;i++){
if(ma.count(pre)){
l=ma[pre];
break;
}else{
v.pu_b(pre);
ma[pre]=i;
r=i;
pre=(pre*10+1)%k;
}
}
int last;
if(n<=r){
for (int i = 1; i <= n; i++){
cnt[v[i]]++;
}
last=v[n];
}else{
for (int i = 1; i <= r; i++){
cnt[v[i]]++;
}
n-=r;
ll shang=n/(r-l+1),yu=n%(r-l+1);
for (int i = l; i <= r; i++){
cnt[v[i]]+=shang;
}
for (int i = 1; i <= yu; i++){
cnt[v[l+i-1]]++;
}
if(yu)last=v[l+yu-1];
else last=v[r];
}
dp[1][last]=1;
for (int i = 0; i < k; i++){//要增加的块
for (int j = 8; j >0; j--){//从后往前,避免重复计数
for (int p = 0; p < k; p++){
for (int q = 1; q <= 9-j; q++){
dp[j+q][(p+i*q)%k]=(dp[j+q][(p+i*q)%k]+dp[j][p]*C(cnt[i]+q-1,q)%mod)%mod;
}
}
}
}
ll ans=0;
for (int i = 1; i <= 9; i++){
ans=(ans+dp[i][0])%mod;
}
out(ans,1);
return 0;
}
## 2019-牛客day1-XOR ### 题意 给 $n(1\le n\le 1e5)$ 个数 $0\le a_i\le1e18$ ,求 $\Large\left(\sum_{S \subseteq A, \oplus_{x \in S} x=0}|S|\right) \bmod \left(10^{9}+7\right)$ ### 题解 对一个数 $a_i$ ,我们求其贡献,将其加入另外 $n-1$ 个数构成的线性基,如果能加入则没有贡献,如果不能加入,则其贡献为 $\large2^{n-1-cnt}$ ,因为对于除线性基外的 $n-1-cnt$ 个数构成的任意集合与 $a_i$ 的异或和都能够由**线性基里的一些元素**异或得到。 维护一个后缀线性基,在从前往后处理,若当前值为 $a_i$ ,前缀线性基为 $pre$ ,若 $a_i$ 不能加入线性基,则其对应的 $cnt$ 为所有 $n$ 个元素的线性基的元素个数,反之,我们需要将 $pre$ 与 $suf[i+1]$ 合并来求 $cnt$ 。显然能加入的个数只有 $\mathcal O(\log a)$ ,所以复杂度为 $\mathcal{O}(n\log a+\log ^3a)$ ### 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
struct da{
ll d[70];
int cnt;
bool add(ll x){
for(int i=63;i>=0;i--){
if(x&(1ll<<i)){
if(d[i])x^=d[i];
else{
cnt++;
d[i]=x;
return 1;
}
}
}
return 0;
}
void merge(da y){
for(int i=63;i>=0;i--){
if(y.d[i])add(y.d[i]);
}
}
void init(){
cnt=0;
memset(d,0,sizeof(d));
}
}suf[N],tm;
ll po[N];
ll a[N];

int main() {
int n;
ll ans;
po[0]=1;
for (int i = 1; i < N; i++)po[i]=po[i-1]*2%mod;
while(~in(n)){
tm.init();
ans=0;
for (int i = 0; i < n; i++)in(a[i]);
suf[n].init();
for(int i=n-1;i>=0;i--){
suf[i]=suf[i+1];
suf[i].add(a[i]);
}
for (int i = 0; i < n; i++){
da tt=tm;
if(tt.add(a[i])){//最多只有60次
suf[i+1].merge(tm);
if(!suf[i+1].add(a[i])){
ans=(ans+po[n-1-suf[i+1].cnt])%mod;
}
}else{
ans=(ans+po[n-1-suf[0].cnt])%mod;
}
tm.add(a[i]);
}
out(ans,1);
}
return 0;
}

UVA - 10535 - Shooter

题意

给 $n$ 个线段,给一个点,从这个点引一条射线,问最多可以与几个线段相交

题解

  • 法一:

    暴力枚举方向( $2*n$ 个端点)

  • 法二:

    对所有端点极角排序,这样问题就转化成了区间最大覆盖问题了,注意因为极角排序会将点按照 $[-\pi,\pi]$ 排序,我们需要特殊考虑跨越 $-\pi,\pi$ 的区间,还有当两点的极角相同时,要将区间的左端点放在前面

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
struct Point{
ll x,y;
int id;
bool type;
int ty2;
Point(){}
Point(ll _x,ll _y) : x(_x),y(_y){}
Point operator - (const Point &k1) const{return Point(x-k1.x,y-k1.y);}
db operator ^ (const Point b) const{return x * b.y - y * b.x;}//叉积,顺时针为负
bool operator < (const Point k1) const{//极角排序
int p1=sign(y)==1||(sign(y)==0&&sign(x)==-1),p2=sign(k1.y)==1||(sign(k1.y)==0&&sign(k1.x)==-1);
if(p1==p2&&sign(*this^k1)==0){
return ty2<k1.ty2;
}
return p1<p2||(p1==p2&&sign(*this^k1)>0);
}
};
typedef Point Vector;
bool vis[N];
Vector a[N];
int main() {
int n;
while(~in(n)&&n){
memset(vis,0,sizeof(vis));
for (int i = 0; i < 2*n; i+=2){
in(a[i].x,a[i].y);
in(a[i+1].x,a[i+1].y);
a[i].id=a[i+1].id=i/2;
}
int x,y;
in(x,y);
int cnt=0;
for (int i = 0; i < 2*n; i+=2){
a[i].x-=x;
a[i].y-=y;
a[i+1].x-=x;
a[i+1].y-=y;
if(a[i].y>a[i+1].y)swap(a[i],a[i+1]);
if(a[i].y<0&&a[i+1].y>=0&&sign((a[i].x*a[i+1].y-a[i].y*a[i+1].x)/(db)(a[i+1].y-a[i].y))==-1){
a[i].type=a[i+1].type=1;
cnt++;
if(a[i]<a[i+1]){
a[i].ty2=1;a[i+1].ty2=0;
}else{
a[i].ty2=0;a[i+1].ty2=1;
}
}else {
a[i].type=a[i+1].type=0;
if(a[i]<a[i+1]){
a[i].ty2=0;a[i+1].ty2=1;
}else{
a[i].ty2=1;a[i+1].ty2=0;
}
}
}
sort(a,a+2*n);
int ans=cnt;
for (int i = 0; i < 2*n; i++){
if(a[i].type){
if(vis[a[i].id]){
cnt++;
ans=max(ans,cnt);
}else{
cnt--;
vis[a[i].id]=1;
}
}else{
if(vis[a[i].id]){
cnt--;
}else{
vis[a[i].id]=1;
cnt++;
ans=max(ans,cnt);
}
}
}
out(ans,1);
}
return 0;
}

题目

题意

题解

代码

1
2


CF-1203F

Posted on 2019-08-15 | In cf | Views:

题意

给两个序列 $a[],b$ ,你拥有的技能点为 $r(1\le r\le 3e4)$ ,只有当技能点 $\ge a_i$ 时,才可以做第 $i$ 份工作,技能点加上 $b_i$

  1. 问他能否完成这 $n$ 份工作
  2. 如果不能,最多能完成几份
Read more »

2019牛客多校-Day1

Posted on 2019-08-13 | Edited on 2019-09-26 | In 牛客 | Views:

B. Integration

题意

已知 $\Large \int_{0}^{\infty} \frac{1}{1+x^{2}} \mathrm{d} x=\frac{\pi}{2}$ ,求 $\Large\frac{1}{\pi} \int_{0}^{\infty} \frac{1}{\prod_{i=1}^{n}\left(a_{i}^{2}+x^{2}\right)} \mathrm{d} x$

Read more »

2019牛客多校-Day4

Posted on 2019-08-09 | Edited on 2019-09-20 | In 牛客 | Views:

I. String

题意

定义两个字符串不等为 $a\ne b \&\& a\ne rev(b) $

给一个字符串,问由它的子串构成的 任意两个元素都不等 的最大集合有多大

Read more »

HDU-6635

Posted on 2019-08-08 | In hdu | Views:

题意

给两个排列 $a , b$ ,$a$ 一开始全都封闭,从左到右遍历 $b$ ,每次释放一个 $\Large a_{b_i}$ ,问每个时刻,释放后的序列 $a$ 的 $LIS$

Read more »

计蒜客-MaxAnswer

Posted on 2019-08-08 | Views:

题意

求数组的一个子区间,使得 子区间之和 乘以 子区间最小值 最大,求最大值

Read more »
1234…9
PerpEternal

PerpEternal

81 posts
12 categories
27 tags
Links
  • GuYF's Blog
© 2019 PerpEternal