ACM模板大全

数论

最大公约数

gcd

int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }

lcm

long long lcm(int a, int b) { return 1ll * a / gcd(a, b) * b; }

线性筛

​ 从小到大枚举因子

​ p[i] : i的最小素因子

​ prime[i]:素数的值

void init(int n) {
    p[1] = 1;
    for(int i = 2; i <= n; i ++) {
        if(!p[i]) p[i] = i, prime[++tot] = i;
        for (int j = 1; j <= tot && prime[j] * i <= n; j ++) {
            p[i * prime[j]] = prime[j];
            if(p[i] == prime[j]) break;
        }
    }
}

扩展欧几里得

​ 用来求解不定方程
a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)

int exgcd(int a, int b, int &x, int &y) {
    if(b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}

​ 求解非负整数解(x, y),输出x最小的解
a x + b y = d ax+by=d ax+by=d

cin >> a >> b >> m;
ll d = exgcd(a, b, x, y);
if(m % d != 0) {
    cout << -1 << "\n";
    continue;
}
a /= d; b /= d; m /= d;
__int128 xx = (__int128)x * m;
xx %= b;
if (xx < 0) xx += b;
__int128 yy = (m - a * xx) / b;
if(yy < 0) {
    cout << -1 << "\n";
    continue;
}
cout << (ll)xx << " " << (ll)yy << "\n";

算数基本定理

​ 任何一个大于1的自然数N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积


N = p 1 e 1 ∗ p 2 e 2 ∗ p 3 e 3 . . . ∗ p n e n ( p 1 < p 2 < p 3 < . . . < p n ) N={p_{1}}^{e_{1}}*{p_{2}}^{e_{2}}*{p_{3}}^{e_{3}}...*{p_{n}}^{e_{n}} (p_{1} < p_{2} < p_{3} < ...N=p1e1p2e2p3e3...pnen(p1<p2<p3<...<pn)

欧拉函数

​ 对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目。
φ ( x ) = x ∗ ∏ i = 1 n ( 1 − 1 p i ) \varphi (x) = x*\prod_{i = 1}^{n}(1-\frac{1}{p_{i}}) φ(x)=xi=1n(1pi1)

int phi(int n) {
    int phin = n, res = n;
    for(int i = 2;  i * i <= n; i++) {
        if(n % i == 0) {
            phin = phin / i * (i - 1);
            while(n % i == 0) n /= i;
        }
    }
    if(n > 1) phin = phin / n * (n - 1);
    return phin;
}

欧拉定理

设a,m都为正整数,且gcd(a,m) = 1,则有
a φ ( m ) ≡ 1 ( m o d   m ) a^{\varphi (m)}\equiv 1(mod\ m) aφ(m)1(mod m)

逆元

​ 任意整数a和其逆元满足
a a − 1 ≡ 1 ( m o d   n ) aa^{-1}\equiv 1(mod\ n) aa11(mod n)
​ 在对除法运算进行取模时,利用逆元有 (a / b) mod p = a * inv(b) (mod p).

​ 费马小定理求逆元(模数为素数时)

long long quickpow(long long a, long long b) {
    if (b < 0) return 0;
    long long ret = 1;
    a %= mod;
    while(b) {
        if (b & 1) ret = (ret * a) % mod;
        b >>= 1;
        a = (a * a) % mod;
    }
    return ret;
}
long long inv(long long a) {
    return quickpow(a, mod - 2);
}

​ 扩展欧几里得求逆元

int getInv(int a, int mod) {
    int x, y;
    int d = exgcd(a, mod, x, y);
    return d == 1 ? (x % mod + mod) % mod : -1;
}

​ 欧拉定理同样可以求逆元

​ 求解1到n的逆元

    inv[1] = 1;
    for(int i = 2; i <= n; i++) {
        inv[i] = (p - p / i) * inv[p % i] % p;
    }

​ 求解阶乘逆元

void init()
{
    fac[0] = 1;
    for (int i = 1; i < maxn; i++)
    {
        fac[i] = fac[i - 1] * i % mod;
    }
    inv[maxn - 1] = quick_pow(fac[maxn - 1],mod - 2,mod);
    for (int i = maxn - 2; i >= 0; --i)
    {
        inv[i] = inv[i + 1] * (i + 1) % mod;
    }
}

求n个数的逆元

    s[0] = 1;
    for (int i = 1; i <= n; i++) s[i] = s[i - 1] * a[i] % p;
    int x, y;
    exgcd(s[n], p, x, y);
    if (x < 0) x += p;
    t[n] = x;
    assert(s[n] * x % p == 1);
    for(int i = n; i >= 1; i --) t[i - 1] = t[i] * a[i] %p;
    for(int i = 1; i <= n; i++) {
        inv[i] = s[i - 1] * t[i] % p;
    }

中国剩余定理

整除分块

for(ll l = 1; l <= n; l ++) {
    ll d = n / l, r = n / d;
    sum += (r - l + 1) * d;
    // l .. r  n / x = d
    l = r;
}

long long 取模

ll mul(ll x, ll y, ll m) {
    x %= m; y %= m;
    ll d = ((long double) x * y / m);
    d = x * y - d * m;
    if (d >= m) d -= m;
    if(d < 0) d += m;
    return d;
}

Lucas定理

long long Lucas(long long n, long long m, long long p) {
    if(m == 0) return 1;
    return (c(n % p, m % p, p) * Lucas(n / p, m / p, p)) % p;
}

扩展欧拉定理

a b   %   m = a b %   φ ( m ) + φ ( m )   %   m a^{b}\ \% \ m = a^{b \% \ \varphi(m) + \varphi(m)}\ \%\ m ab % m=ab% φ(m)+φ(m) % m

积性函数

定义:(a,b) = 1, f(ab) = f(a)f(b)

常见的积性函数

i d ( x ) = x id(x) = x id(x)=x
2.
1 ( x ) = 1 1(x) = 1 1(x)=1
3.
e ( x ) = { 1 x = 1 0 x ≠ 1 e(x) = \left\{\begin{matrix} 1& x = 1 & \\ 0& x\neq 1 & \end{matrix}\right. e(x)={10x=1x=1
4.欧拉函数
φ ( n ) = n ∑ p ∣ n ( 1 − 1 p ) \varphi (n) = n\sum_{p | n}(1-\frac{1}{p}) φ(n)=npn(1p1)
5.d(n)因子个数
d ( p e ) = e + 1 d(p^{e}) = e + 1 d(pe)=e+1
6.
σ ( p e ) = p 0 + p 1 + . . . + p e \sigma (p^{e}) = p^{0} + p^{1} + ...+p^{e} σ(pe)=p0+p1+...+pe
7.莫比乌斯函数
μ ( p e ) = { 1 e = 0 − 1 e = 1 0 e ≥ 2 \mu (p^{e})=\left\{\begin{matrix} 1 & e=0 \\ -1 & e=1\\ 0&e\geq 2 \end{matrix}\right. μ(pe)= 110e=0e=1e2

性质
∑ d ∣ n μ ( d ) = [ n = 1 ] \sum_{d|n}\mu(d)=[n=1] dnμ(d)=[n=1]

线性筛求积性函数


f ( n ) = f ( p 1 e 1 ) ∗ f ( p 2 e 2 ) ∗ . . . ∗ f ( p k e k ) f(n) = f(p_{1}^{e_{1}})*f(p_{2}^{e_{2}}) *...*f(p_{k}^{e_{k}}) f(n)=f(p1e1)f(p2e2)...f(pkek)

f ( n ) = f ( p 1 e 1 ) ∗ f ( n / p 1 e 1 ) f(n) = f(p_{1}^{e_{1}})*f(n/p_{1}^{e_{1}}) f(n)=f(p1e1)f(n/p1e1)

const int N = 2e7 + 1000;
int p[N], pr[N / 5], n, pe[N], tot;
int f[N], a, b, ans;
void prime() {
    p[1] = 1;
    for(int i = 2; i <= n; i ++) {
        if(!p[i]) p[i] = i, pe[i] = i, pr[++tot] = i;
        for(int j = 1; j <= tot && pr[j] * i <= n; j ++) {
            p[i * pr[j]] = pr[j];
            if (p[i] == pr[j]) {
                pe[i * pr[j]] = pe[i] * pr[j];
                break;
            } else {
                pe[i * pr[j]] = pr[j];
            }
        }
    }
}

void compute(int n, function<void(int)> calcpe) {
    f[1] = 1;
    for(int i = 2; i <= n; i ++) {
        if(i == pe[i]) calcpe(i);
        else f[i] = f[pe[i]] * f[i / pe[i]];
    }
}
//因子个数
compute(n, [&](int x){
        f[x] = f[x / p[x]] + 1;
});
//因子和
compute(n, [&](int x){
       f[x] = f[x / p[x]] + x;
});
//欧拉函数
compute(n, [&](int x){
      f[x] = x / p[x] * (p[x] - 1);	
});
//mo'b
compute(n, [&](int x){
      f[x] = x == p[x] ? -1 : 0;
});

不同质因子个数

​ 求一个数不同的质因子个数 p[i]。(加性函数)

void init(int n) {
    p[1] = 1;
    for(int i = 2; i <= n; i ++) {
        if(!vis1[i]) p[i] = 1, prime[++tot] = i;
        for (int j = 1; j <= tot && prime[j] * i <= n; j ++) {
            vis1[i * prime[j]] = 1;
            if(i % prime[j] == 0) {
                p[i * prime[j]] = p[i];
                break;
            }
            p[i * prime[j]] = p[i] + p[prime[j]];
        }
    }
}

迪利克雷卷积

定义

h ( n ) = ∑ d ∣ n f ( d ) g ( n / d ) = ∑ d 1 d 2 = n f ( d 1 ) g ( d 2 ) h(n) = \sum_{d|n}f(d)g(n/d) = \sum_{d_{1}d_{2} = n}f(d_{1})g(d_{2}) h(n)=dnf(d)g(n/d)=d1d2=nf(d1)g(d2)

常见的卷积

1.d(n) 因子个数
d ( n ) = ∑ d ∣ n 1 ( d ) 1 ( d / n ) d(n) = \sum_{d|n}1(d)1(d/n) d(n)=dn1(d)1(d/n)
2.因子和
σ ( n ) = ∑ d ∣ n i d ( d ) 1 ( n / d ) \sigma(n) = \sum_{d|n}id(d)1(n/d) σ(n)=dnid(d)1(n/d)
3.
f = f ∗ e f = f*e f=fe
4.
e = 1 ∗ μ e = 1*\mu e=1μ

性质

1.交换律
h = f ∗ g = g ∗ f h = f*g = g*f h=fg=gf
2.结合律
p = ( f ∗ g ) ∗ h = f ∗ ( g ∗ h ) p=(f*g)*h=f*(g*h) p=(fg)h=f(gh)
3.f和g是积性函数,则f*g也是积性函数

莫比乌斯反演

形式

f ( n ) = ∑ d ∣ n g ( d ) < = > g ( n ) = ∑ μ ( n / d ) f ( d ) f(n) = \sum_{d|n}g(d) <=>g(n) = \sum \mu(n/d)f(d) f(n)=dng(d)<=>g(n)=μ(n/d)f(d)

f = g ∗ 1 < = > g = f ∗ u f = g*1 <=>g=f*u f=g1<=>g=fu

一些经典的反演

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5pPUCZJK-1673443114471)(C:\Users\xsf\AppData\Roaming\Typora\typora-user-images\image-20221101224706988.png)]

在求
∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = 1 ] ( n < m ) \sum^{n}_{i=1}\sum^{m}_{j=1}[gcd(i,j)=1](ni=1nj=1m[gcd(i,j)=1](n<m)
时,可以通过上式变化为[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yKqcDp2c-1673443114476)(C:\Users\xsf\AppData\Roaming\Typora\typora-user-images\image-20221101225037605.png)]化简得[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e2MAE7JB-1673443114476)(C:\Users\xsf\AppData\Roaming\Typora\typora-user-images\image-20221101225209016.png)]该式可以通过整除分块O(根号n)求解

形如[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zBwLndZG-1673443114477)(C:\Users\xsf\AppData\Roaming\Typora\typora-user-images\image-20221101225357216.png)]同样可以通过上述方法求解

code如下:

//P3455
#include 
using namespace std;
using ll = long long;
const int N = 1e6 + 1000;
int p[N], pr[N / 5], n, pe[N], tot;
int f[N], smu[N], a, b, ans;
void prime() {
    p[1] = 1;
    for(int i = 2; i <= n; i ++) {
        if(!p[i]) p[i] = i, pe[i] = i, pr[++tot] = i;
        for(int j = 1; j <= tot && pr[j] * i <= n; j ++) {
            p[i * pr[j]] = pr[j];
            if (p[i] == pr[j]) {
                pe[i * pr[j]] = pe[i] * pr[j];
                break;
            } else {
                pe[i * pr[j]] = pr[j];
            }
        }
    }
}

void compute(int n, function<void(int)> calcpe) {
    f[1] = 1;
    for(int i = 2; i <= n; i ++) {
        if(i == pe[i]) calcpe(i);
        else f[i] = f[pe[i]] * f[i / pe[i]];
    }
}//莫比乌斯函数求解

int main() {
    int q;
    cin >> q;
    n = 5e4 + 10;
    prime();
    compute(n, [&](int x){
      f[x] = x == p[x] ? -1 : 0;
    }); 
    for(int i = 1; i <= n; i ++) {
        smu[i] = smu[i - 1] + f[i];
    }
    while(q --) {
        int a, b, k;
        cin >> a >> b >> k;
        a /= k, b /= k;
        if(a > b) swap(a, b);
        ll ans = 0;
        for(int l = 1; l <= a; l ++) {
            int r = min(a / (a / l), b / (b / l));
            ans += (1ll*smu[r] - smu[l - 1]) * (a / l) * (b / l);
            l = r; 
            // cout << l << endl;
        }//整除分块
        cout << ans << endl;
    }
}

高斯消元

异或高斯消元

int xor_gauss() {
    int r = 1;
    for (int c = n; c >= 1; c--) {
        int temp = r;
        for (int i = r; i <= n; i++)
            if (a[i][c]) {
                temp = i;
                break;
            }
        if (!a[temp][c]) continue;
        swap(a[r], a[temp]);
        for (int i = r + 1; i <= n; i++) {
            if (a[i][c]) a[i] ^= a[r];
        }
        r++;
    }
    if (r <= n) {
        for (int i = 1; i <= n; i++) {
            if (a[i] == 1) return -1;
            if (a[i] == 0) return quick_pow(2, n - i  + 1, mod);
        }
    }
    for (int i = 1; i <= n; i++) {
        int res = a[i][0];
        for (int j = i - 1; j; j--)
            res ^= a[i][j] ^ ans[j];
        ans[i] = res;
    }
    return 1;
}

高斯消元求行列式 (取模)

int gauss(int a[N][N],int n)
{
	int res=1;
	for(int i=1; i<=n; i++)
	{
		for(int j=i+1; j<=n; j++)
		{
			while(a[j][i])
			{
				int t=a[i][i]/a[j][i];
				for(int k=i; k<=n; k++)
					a[i][k]=(a[i][k]-t*a[j][k]+mod)%mod;
				swap(a[i],a[j]);
				res=-res;
			}
		}
		res=(res*a[i][i])%mod;
	}
	return (res+mod)%mod;
}

图论

邻接表建图

void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

最短路

floyd

传递闭包

for(int k = 1; k <= n; k ++)
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                d[i][j] |= d[i][k] & d[k][j];

LCA

倍增求LCA

int fa[N][16]
void bfs() {
    memset(depth, 0x3f, sizeof depth);
    depth[root] = 1, depth[0] = 0;
    int hh = 0, tt = -1;
    q[++tt] = root;
    while(hh <= tt) {
        int t = q[hh ++];
        for(int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if(depth[j] > depth[t] + 1) {
                depth[j] = depth[t] + 1;
                q[++tt] = j;
                fa[j][0] = t;
                for(int k = 1; k <= 15; k++) 
                    fa[j][k] = fa[fa[j][k - 1]][k - 1];
            }
        }
    }
}

int lca(int a, int b) {
    if(depth[a] < depth[b]) swap(a, b);
    for(int k = 15; k >= 0; k--) 
        if(depth[fa[a][k]] >= depth[b]) 
            a = fa[a][k];
    if(a == b) return a;
    for(int k = 15; k >= 0; k --)
        if(fa[a][k] != fa[b][k]) {
            a = fa[a][k];
            b = fa[b][k];
        }
    return fa[a][0];
}

树上差分

点差分

将两点u,v之间路径上的所有点权增加x, o = LCA(u, v), o的父亲结点为p,则:
d i f f [ u ] + = x , d i f f [ v ] + = x , d i f f [ o ] − = x , d i f f [ p ] − = x ; diff[u]+=x,diff[v]+=x,diff[o]-=x,diff[p]-=x; diff[u]+=x,diff[v]+=x,diff[o]=x,diff[p]=x;
code

void dfs(int u, int fa) {  
    int res = 0;  
    for(int i = h[u]; ~i; i = ne[i]) {  
        int j = e[i];  
        if(j == fa) continue;  
        dfs(j, u);  
        res += sum[j];  
    }   
    sum[u] = res + diff[u];  
}    
cin >> u >> v;  
int x = lca(u, v);  
diff[u] += 1;  
diff[v] += 1;   
diff[x] -= 1;  
diff[p[x]] -= 1;  

边差分

将两点u, v之间路径上的边权增加x,o = LCA( u,v),以每条边两端深度较大的节点存储改边的差分数组,则操作如下:
d i f f [ u ] + = x , d i f f [ v ] + = x , d i f f [ o ] − = 2 ∗ x ; diff[u]+=x,diff[v]+=x,diff[o]-=2*x; diff[u]+=x,diff[v]+=x,diff[o]=2x;

Tarjan缩点

有向图的强连通分量

连通分量:对于分量中任意两点u,v必然可以从u走到v,且从v走到u

强连通分量:极大连通分量

通过Tarjan缩点可以让有向图转变为有向无环图,转变后的图里面的每一个点是原图的一个强连通分量。

一个有向图,变成一个强连通分量至少需要添加max(p,q)条边,p为缩点后入度为0的点,q为缩点后出度为0的点

int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, size[N];

void tarjan(int u) {
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top ] = u, in_stk[u] = 1;
    for(int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if(!dfn[j]) {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        }
        else if(in_stk[j]) low[u] = min(low[u], dfn[j]);
    }

    if(dfn[u] == low[u]) {
        ++ scc_cnt;
        int y;
        do {
            y = stk[top -- ];
            in_stk[y] = 0;
            id[y] = scc_cnt;
            size[scc_cnt] ++;
        } while(y != u);
    } 
}

差分约束

for(int i = 1; i <= m; i++) {
    int f, x, y;
    std::cin >> f >> x >> y;
    if(f == 1) { // 等于 x == y, x -> y, w = 0, y -> x, w = 0;
        add(h,x,y,0);add(h,y,x,0);
    } else if(f == 2) { // 小于 x < y, x->y, w = 1;
        add(h,x,y,1);
    } else if(f == 3) { // 大于等于 x >= y, y->x, w = 0;
        add(h,y,x,0);
    } else if(f == 4) { // 大于 x > y, y->x, w = 1;
        add(h,y,x,1);
    } else if(f == 5) { // 小于等于 x <= y, x->y, w = 0;
        add(h,x,y,0);
    } 
}

for(int i = 1; i <= n; i++) {
    add(h,0,i,1);
}
// 缩点
tarjan(0);
// 建DAG
int f = 1;
for(int i = 0; i <= n; i ++) {
    for(int j = h[i]; ~j; j = ne[j]) {
        int k = e[j];
        int u = id[i], v = id[k];
        if(u == v) {
            // 不符合不等式关系
            if(w[j] > 0) {
                f = 0;
                break;
            }
        } else add(h1, u, v, w[j]);
    }
    if(!f) break;
}
if(!f) {
    std::cout << "-1\n";
    return 0;
}
// DAG求最长路
for(int i = scc_cnt; i >= 0; i --) {
    for(int j = h1[i]; ~j; j = ne[j]) {
        int k = e[j];
        d[k] = std::max(d[i] + w[j], d[k]);
    }
}

无向图的双连通分量

边双连通分量 e-Dcc

​ 无向图中,极大的不含有桥的连通块被称为边的双连通分量

​ 在里面不管删掉哪条边,仍然连通。

​ 每对点之间至少存在两条没有公共边的路径

​ 一个无向图变成边双连通分量,至少需要添加(cnt + 1)/ 2 条边,cnt为缩点后度数为1的点的个数

int dfn[N], low[N], timestamp;  // 时间戳
int stk[N], top;
int id[N], dcc_cnt;  // 每个点所属分量编号
bool is_bridge[M];

void tarjan(int u, int from)
{
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u;
    
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            if (dfn[u] < low[j])
                is_bridge[i] = is_bridge[i ^ 1] = true;
        }
        else if (i != (from ^ 1))
            low[u] = min(low[u], dfn[j]);
    }
    
    if (dfn[u] == low[u])
    {
        ++ dcc_cnt;
        int y;
        do {
            y = stk[top -- ];
            id[y] = dcc_cnt;
        } while (y != u);
    }
}

点双连通分量 v-Dcc

​ 极大的不包含割点的连通块被称为点的双连通分量

​ 每个割点至少属于两个点双连通分量

二分图

1.二分图不存在奇数环,染色法不存在矛盾

2.匈牙利算法,匹配,最大匹配,匹配点,增广路径

3.最大匹配数 = 最小点覆盖 = 总点数 - 最大独立集 = 总点数 - 最小路径覆盖

匈牙利算法

bool find(int u) {  
    for(auto v : eg[u]) {  
        if(!st[v]) {  
            st[v] = 1;  
            int t = match[v];  
            if(t == 0 || find(t)) {  
                match[v] = u;  
                return true;  
            }  
        }  
    }  
    return false;  
}  	
for(int i = 1; i <= n; i++) {  
    memset(st, 0, sizeof st);  
    ans += find(i);  
}

最小路径覆盖 DAG

不相交

​ DAG,有向无环图,用最少的互不相交的路径,将所有点覆盖。

​ 拆点,将原图中1…n,拆成新图1…n,1’…n’,原图i->j的有向边变成新图中i->j’的无向边,新图一定为二分图。

​ (实际上不需要新建一张图)

​ 最小路径覆盖 = 原图点数 - 新图最大匹配数

可相交

​ 用最少的可相交的路径,将所有点覆盖

​ 对原图G,先求出传递闭包G’,然后对G’转化为不相交问题

code

//floyd求传递闭包
for(int k = 1; k <= n; k ++)
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                d[i][j] |= d[i][k] & d[k][j];
int ans = 0;
//匈牙利算法
for(int i = 1; i <= n; i ++) {
    memset(st, 0, sizeof st);
    ans += find(i);
}

cout << n - ans << "\n";

字符串

AC自动机

//有多少个不同的模式串在文本串里出现过 
namespace AC {
    int tr[N][26], tot;
    int e[N], fail[N];

    void insert(char *s) {
        int u = 0;
        for (int i = 1; s[i]; i ++) {
            if(!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
            u = tr[u][s[i] - 'a'];
        }
        e[u] ++;
    }

    queue<int> q;

    void build() {
        for(int i = 0; i < 26; i++)
            if(tr[0][i]) q.push(tr[0][i]);
        while (q.size()) {
            int u = q.front();
            q.pop();
            for (int i = 0; i < 26; i ++) {
                if (tr[u][i]) {
                    fail[tr[u][i]] = tr[fail[u]][i];
                    q.push(tr[u][i]);
                } else tr[u][i] = tr[fail[u]][i];
            }
        } 
    }

    int query(char *t) {
        int u = 0, res = 0;
        for (int i = 1; t[i]; i++) {
            u = tr[u][t[i] - 'a'];
            for (int j = u; j && e[j] != -1; j = fail[j]) {
                res += e[j], e[j] = -1;
            }
        }
        return res;
    }
}

//AC自动机
#include 
using namespace std;

const int N = 2e6 + 10;
char s[N];
char a[N];
namespace AC {
    int tr[N][26], tot;
    int idx[N], fail[N];
    int val[N];
    int vis[N];
    int cnt[N]; // 第i个字符串的出现次数

    void insert(char *s, int id) {
        int u = 0;
        for (int i = 1; s[i]; i ++) {
            if(!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
            u = tr[u][s[i] - 'a'];
        }
        vis[id] = u;
    }

    void init() {
        memset(fail, 0, sizeof fail);
        memset(tr, 0, sizeof tr);
        memset(idx, 0, sizeof idx);
        memset(val, 0, sizeof val);
        memset(cnt, 0, sizeof cnt);
        tot = 0;
    }

    queue<int> q;

    void build() {
        for(int i = 0; i < 26; i++)
            if(tr[0][i]) q.push(tr[0][i]);
        while (q.size()) {
            int u = q.front();
            q.pop();
            for (int i = 0; i < 26; i ++) {
                if (tr[u][i]) {
                    fail[tr[u][i]] = tr[fail[u]][i];
                    q.push(tr[u][i]);
                } else tr[u][i] = tr[fail[u]][i];
            }
        } 
    }

    int query(char *t) {
        int u = 0, res = 0;
        for (int i = 1; t[i]; i++) {
            u = tr[u][t[i] - 'a'];
            for (int j = u; j ; j = fail[j]) res val[j] ++;
        }
        return res;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    // AC::init();
    for(int i = 1; i <= n; i ++) {
        cin >> s + 1;
        AC::insert(s, i);
    }
    AC::build();
    cin >> a + 1;
    int x = AC::query(a);
    for(int i = 1; i <= n; i ++) {
        cout << AC::val[AC::vis[i]] << "\n";
    }
}

三.树上差分优化

    int head[N], nxt[N], to[N], cnt;

    void add(int u, int v) {
            nxt[++cnt] = head[u];
            head[u] = cnt;
            to[cnt] = v;
    }
    
    void dfs(int u) {
        int i, v;
        for(i = head[u]; i; i = nxt[i]) {
            v = to[i];
            dfs(v);
            val[u] += val[v];
        }
    }

    int query(char *t) {
        int u = 0, res = 0;
        for (int i = 1; t[i]; i++) {
            u = tr[u][t[i] - 'a'];
            ++val[u];
        }

        for(int i = 1; i <= tot; i ++) add(fail[i], i);

        dfs(0);

        return res;
    }

后缀自动机

节点含义

​ 每个节点代表的是一个连续长度的字符串的集合,这个集合里面的字符串的长度是连续的,并且每个长度都只会出现一次,字符串集合里面字符串都是长度比它大的字符串的后缀

Fail指针

​ 跳到比当前节点短的最长的相同后缀字符串集

​ Fail[x]的字符串集长度和x的字符串集长度是连续的

struct Suffix_Automata {
    //两倍字符串长度的空间
    int maxlen[Maxn], trans[Maxn][26], link[Maxn], Size, Last;
    int a[Maxn], b[Maxn], endpos[Maxn];
    int dp[Maxn], sum[Maxn];
    Suffix_Automata() {Size = Last = 1;}

    inline void Extend(int id) {
        int cur = (++ Size), p;
        maxlen[cur] = maxlen[Last] + 1;
        endpos[cur] = 1;
        for(p = Last; p && !trans[p][id]; p = link[p]) trans[p][id] = cur;
        if (!p) link[cur] = 1;
        else {
            int q = trans[p][id];
            if (maxlen[q] == maxlen[p] + 1) link[cur] = q;
            else {
                int clone = (++ Size);
                maxlen[clone] = maxlen[p] + 1;
                for(int i = 0; i < 26; ++i) trans[clone][i] = trans[q][i];
                link[clone] = link[q];
                for(; p && trans[p][id] == q; p = link[p]) trans[p][id] = clone;
                link[cur] = link[q] = clone;
            }
        }
        Last = cur;
    }

    void getTP(int &Len) { //getendpos 前置
        for(int i = 1; i <= Size; i ++) a[maxlen[i]] ++;
        for(int i = 1; i <= Len; i ++) a[i] += a[i - 1];
        for(int i = 1; i <= Size; i++) b[a[maxlen[i]] --] = i;
    }

    void getendpos() { //求每类子串的数量
        for(int i = Size; i >= 1; i --) {
            int e = b[i];
            endpos[link[e]] += endpos[e];
        }
    }

    ll getSubNum() { // 求不相同子串数量
        ll ans = 0;
        for(int i = 2 ;i <= Size; ++i)
            ans += maxlen[i] - maxlen[link[i]] ;
        return ans;
    }

    void get_len_max(int Len) { // 求长度为i的出现次数最多的子串
        for(int i = 1; i <= Size; i++) dp[maxlen[i]] = max(dp[maxlen[i]], endpos[i]);
        for(int i = Len - 1; i >= 1; i --) dp[i] = max(dp[i], dp[i + 1]);
        for(int i = 1; i <= Len; i ++) {
            cout << dp[i] << "\n";
        }
    }

    void getsum() { //getmink前置
        for(int i = Size; i >= 1; i --) {
            int &e = b[i];
            sum[e] = 1;
            for(int j = 0; j < 26; j ++) {
                sum[e] += sum[trans[e][j]];
            }
        }
    }

    void getmink(int k) { // 求字典序第k小的子串
        int now = 1, p;
        string s = "";
        while(k) {
            for (int i = 0; i < 26; i ++) {
                if (trans[now][i] && k) {
                    p = trans[now][i];
                    if (sum[p] < k) k -= sum[p];
                    else {
                        k --; now = p;
                        s += (char)(i + 'a');
                        break;
                    }
                }
            }
        }
        cout << s << "\n";
    }

    void LCS(char s[], int Len) { // 求两个串的最长公共子串
        int ans = 0, cnt = 0;
        int now = 1;
        char base = 'a';
        for(int i = 0; i < Len; i++) {
            int c = s[i] - base;
            if (trans[now][c]) {
                cnt ++;
                now = trans[now][c];
            } else {
                while(now&&!trans[now][c]) now = link[now];
                if (!now) cnt=0, now=1;
                else cnt = maxlen[now]+1, now = trans[now][c];
            }
            ans = max(ans, cnt);
        }
        cout << ans << "\n";
    }

    int ans[Maxn];
    void init_ans(){
        for(int i=1;i<=Size;i++) ans[i]= maxlen[i];
    }
    
    void LCS2(char s[],int Len){     //求多个串的最长公共子串 
        for(int i=1;i<=Size;i++) dp[i]=0;
        int cnt=0;
        int now=1;
        char base='a';
        for(int i=0;i<Len;i++){
            int c=s[i]-base;
            if(trans[now][c]){
                cnt++;
                now=trans[now][c];
            }
            else{
                while(now&&!trans[now][c]) now=link[now];
                if(!now) cnt=0,now=1;
                else cnt=maxlen[now]+1,now=trans[now][c];
            }
            dp[now]=max(dp[now],cnt);
        }
        for(int i=Size;i>=1;i--){
            int e=b[i];
            dp[link[e]]=max(dp[link[e]],min(dp[e],maxlen[link[e]]));
        }
        for(int i=1;i<=Size;i++) ans[i]=min(ans[i],dp[i]);
    }
    void get_LCS2_ans(){
        int cnt=0;
        for(int i=1;i<=Size;i++) cnt=max(cnt,ans[i]);
        printf("%d\n",cnt);
    }

    void get_cntk(int k) { //求出现次数为k的子串种数
        ll ans = 0;
        for (int i = 1; i <= Size; i++) {
            if(endpos[i] == k) ans += maxlen[i] - maxlen[link[i]];
        }
        cout << ans << "\n";
    }

    int d[Maxn];
    void get_sumk(int l, int r) { //求出现次数 l <= k <= r 的子串种数
        for(int i = Size; i > 1; i--) {
            int v = b[i];
            if(endpos[v] >= l && endpos[v] <= r) d[v] ++;
            for(int j = 0; j < 26; j ++) {
                if(trans[v][j]) d[v] += d[trans[v][j]];
            }
        }
        ll ans = 0;
        for (int i = 0; i < 26; i ++) {
            if (trans[1][i]) ans += d[trans[1][i]];
        }
        cout << ans << "\n";
    }
} T;

常见技巧

python

输入与输出

第一行包含一个整数 n,表示待处理的整数个数。

第二行包含空格分隔的 n 个整数,依次表示 a1,a2,⋯,an。

n = int(input())
arr = [int(x) for x in sys.stdin.readline().split()]

map

# 初始化
vis = {}
# 是否在vis内
vis.has_key(key)
# 以列表返回可遍历的(键, 值) 元组数组
vis.items()
# 以列表返回一个字典所有的键
vis.keys()
# 删除字典内所有元素
vis.clear()
# 删除字典给定键key对应的值 
vis.pop(key)

你可能感兴趣的:(算法,算法,c++,数论,图论)