当前位置:   article > 正文

ACM模板大全

acm模板

数论

最大公约数

gcd

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

lcm

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

线性筛

​ 从小到大枚举因子

​ 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;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

扩展欧几里得

​ 用来求解不定方程
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

​ 求解非负整数解(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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

算数基本定理

​ 任何一个大于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} < ...<p_{n}) 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

欧拉定理

设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).

​ 当a为b的倍数时,有
a ÷ b m o d p = ( a m o d ( b × p ) ) ÷ b a÷bmodp=(amod(b×p))÷b a÷bmodp=(amod(b×p))÷b

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

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

​ 扩展欧几里得求逆元

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
  • 2
  • 3
  • 4
  • 5

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

​ 求解1到n的逆元

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

​ 求解阶乘逆元

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

求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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

中国剩余定理

整除分块

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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;
}
  • 1
  • 2
  • 3
  • 4

扩展欧拉定理

a b   %   m = a b %   φ ( m ) + φ ( m )   %   m   ,   g c d ( a , m ) ! = 1 & b > = ϕ ( m ) a b % m = a b % m   ,   g c d ( a , m ) ! = 1 & b < ϕ ( m ) a b % m = a b % ϕ ( m ) % m ,   g c d ( a , m ) = 1 a^{b}\ \% \ m = a^{b \% \ \varphi(m) + \varphi(m)}\ \%\ m\ , \ gcd(a,m)!=1\&b>=\phi(m) \\a^{b}\%m=a^b\%m\ ,\ gcd(a,m)!=1\&b<\phi(m) \\a^{b}\%m=a^{b\%\phi(m)}\%m, \ gcd(a,m)=1 ab % m=ab% φ(m)+φ(m) % m , gcd(a,m)!=1&b>=ϕ(m)ab%m=ab%m , gcd(a,m)!=1&b<ϕ(m)ab%m=ab%ϕ(m)%m, gcd(a,m)=1

积性函数

定义:(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\{

1x=10x1
\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\{
1e=01e=10e2
\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;
});
  • 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

不同质因子个数

​ 求一个数不同的质因子个数 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]];
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

迪利克雷卷积

定义

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

一些经典的反演

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在求
∑ 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](n<m) i=1nj=1m[gcd(i,j)=1](n<m)
时,可以通过上式变化为外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传化简得外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传该式可以通过整除分块O(根号n)求解

形如外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传同样可以通过上述方法求解

code如下:

//P3455
#include <bits/stdc++.h>
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;
    }
}
  • 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

BSGS

a和m互质,求解最小的非负整数t,使得下式成立
a t ≡ b (   m o d   m ) a^t \equiv b(\bmod m) atb(modm)
由欧拉定理
a φ ( m ) ≡ 1 (   m o d   m ) a^{\varphi(m)} \equiv 1(\bmod m) aφ(m)1(modm)
说明 a t a^t at在模m的意义下有一个长度为 φ ( m ) \varphi(m) φ(m)的循环节,所以我们只需要考虑 t ≤ φ ( m ) t \leq \varphi(m) tφ(m)的情形就可以了。当m是质数时 φ ( m ) = m − 1 \varphi(m)=m-1 φ(m)=m1所以暴力枚举的复杂度为O(m)。

考虑优化,我们把 t t t拆成 k x − y kx-y kxy k = m + 1 k=\sqrt m + 1 k=m +1 x ∈ [ 1 , k ] x \in[1, k] x[1,k] y ∈ [ 0 , k − 1 ] y \in [0,k-1] y[0,k1]

x = 1 , y = k − 1 x=1,y=k-1 x=1,y=k1时, t = 1 t=1 t=1; 当 x = k , y = 0 x=k,y=0 x=k,y=0时, t = k 2 t=k^2 t=k2 ;这样我们通过遍历x和y,可以让t遍历完 [ 1 , m ] [1,m] [1,m]

用x,y替换t后 a k x − y ≡ b (   m o d   m ) a^{kx-y}\equiv b(\bmod m) akxyb(modm),移项可得, a k x ≡ b ∗ a y (   m o d   m ) a^{kx} \equiv b*a^{y}(\bmod m) akxbay(modm)。我们可以预处理出 b y (   m o d   m ) b^y(\bmod m) by(modm) 的所有值对应的最大的y。然后枚举x,看是否有对应的y和它相等。复杂度为 O ( m ) O(\sqrt{m}) O(m )

cin >> m >> a >> b;
int k = sqrt(m) + 1;
if(b == 1) {
    cout << 0;
    return 0;
}
map<int, int> vis;
int now = 1;
for(int y = 0; y <= k - 1; y ++) {
    vis[(now * b) % m] = y;
    now = 1ll * now * a % m;
}
int cur = now;
for(int x = 1; x <= k; x ++) {
    if(vis.find(now % m) != vis.end()) {
        int y = vis[now % m];
        if(k * x - y >= 0) ans = min(ans, k*x-y);
    }
    now = 1ll * now * cur % m;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

高斯消元

异或高斯消元

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;
}
  • 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

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

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

图论

邻接表建图

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

直径

求树的直径并输出

两次dfs(边权不能为负)

vector<LL> d(N);
vector<int> p(N);
function<void(int, int, LL)> dfs = [&](int u, int par, LL dep) {
    d[u] = dep;
    p[u] = par;
    for (auto [v, w] : G[u]) if (v != par) {
        dfs(v, u, dep + w);
    }
};
dfs(0, -1, 0);
int r = max_element(d.begin(), d.end()) - d.begin();
dfs(r, -1, 0);
vector<int> v;
int s = max_element(d.begin(), d.end()) - d.begin();
for (int x = s; x >= 0; x = p[x]) v.push_back(x);
cout << d[s] << " " << v.size() << "\n";
for (int x : v) cout << x << " ";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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];
}
  • 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
树上差分

点差分

将两点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;  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

边差分

将两点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;

最短路

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];
  • 1
  • 2
  • 3
  • 4

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);
    } 
}
  • 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

差分约束

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]);
    }
}
  • 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

无向图的双连通分量

边双连通分量 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);
    }
}
  • 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
点双连通分量 v-Dcc

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

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

int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;  // 时间戳
int stk[N], top;
int dcc_cnt;
vector<int> dcc[N];  // 每个分量有哪些点
bool cut[N];  // 是否为割点
int root;

void tarjan(int u)
{
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u;
    
    if (u == root && h[u] == -1)
    {
        dcc_cnt ++ ;
        dcc[dcc_cnt].push_back(u);
        return;
    }
    
    int cnt = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            tarjan(j);
            low[u] = min(low[u], low[j]);
            if (dfn[u] <= low[j])
            {
                cnt ++ ;
                if (u != root || cnt > 1) cut[u] = true;
                ++ dcc_cnt;
                int y;
                do {
                    y = stk[top -- ];
                    dcc[dcc_cnt].push_back(y);
                } while (y != j);
                dcc[dcc_cnt].push_back(u);
            }
        }
        else low[u] = min(low[u], dfn[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

二分图

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);  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

最小路径覆盖 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";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

拓扑排序

无向图求环

void topo()
{
	queue<int> q;
	
	for(int i=1;i<=n;i++)
		if(d[i]==1) q.push(i); //不是环内的点,入队
	
	while(!q.empty())
	{
		int u=q.front();
		vis[u]=1;	
		q.pop();
		for(int i=0;i<g[u].size();i++)
		{
			int v=g[u][i];
			if(--d[v]==1) q.push(v); //u和v断开,v的度数-1,减一后若度数变为,则可以入队
		}
	}
}
// 度数为2的点是环内的点
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

欧拉回路和欧拉路径

无向图

​ 对所有边都连通的无向图

​ (1)存在欧拉路径的充分必要条件:度数为奇数的点只能有0或2个。

​ (2)存在欧拉回路的充分必要条件:度数为奇数的点只能有0个。

void dfs(int u) {
    for(int i = 1; i <= 500; i ++) {
        if(g[u][i]) {
            g[u][i] --, g[i][u] --;
            dfs(i);
        }
    }
    ans[++ cnt] = u;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

有向图

​ 对所有边都连通的有向图

​ (1)存在欧拉路径的充分必要条件:要么所有点的出度均等于入度;要么除了两个点之外,其余所有点的出度等于入度,剩余的两个点:一个满足出度比入度多1(起点),另一个满足入度比出度多1(终点)。

​ (2)存在欧拉回路的充分必要条件:所有点的出度均等于入度。

数据结构

并查集

int get(int x) {
	if(fa[x] != x) fa[x] = get(fa[x]);
	return fa[x];
}

void merge(int x, int y) {
	x = get(x), y = get(y);
	if(x == y) return;
	fa[x] = y;
	val[y] += val[x];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

左偏树

struct node {
	int ls, rs, d;
	ll cost, sum, siz;
}t[N];
int id[N], val[N], cost[N];

void pushup(int x) {
	t[x].sum = t[t[x].ls].sum + t[t[x].rs].sum + t[x].cost;
	t[x].siz = t[t[x].ls].siz + t[t[x].rs].siz + 1;
}

int merge(int x, int y) {
  if (!x || !y) return x | y;  // 若一个堆为空则返回另一个堆
  if (t[x].cost < t[y].cost) swap(x, y);  // 取值较大的作为根
  t[x].rs = merge(t[x].rs, y);          // 递归合并右儿子与另一个堆
  if (t[t[x].rs].d > t[t[x].ls].d)
    swap(t[x].ls, t[x].rs);   // 若不满足左偏性质则交换左右儿子
  t[x].d = t[t[x].rs].d + 1;  // 更新dist
  pushup(x);
  return x;
}

void build() {
	for(int i = 1; i <= n; i ++) id[i] = i, t[i].siz = 1, t[i].cost = cost[i], t[i].sum = cost[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

树状数组

int lowbit(int x)
{
    return x & -x;
}

void update(int x, int c)  // 位置x加c
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int ask(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

线段树

struct Node
{
    int l, r;
    // TODO: 需要维护的信息和懒标记
}tr[N * 4];

void pushup(int u)
{
    // tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
    // TODO: 利用左右儿子信息维护当前节点的信息
}

void pushdown(int u)
{
    // TODO: 将懒标记下传
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void update(int u, int l, int r, int d)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        // TODO: 修改区间
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) update(u << 1, l, r, d);
        if (r > mid) update(u << 1 | 1, l, r, d);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        return ;  // TODO 需要补充返回值
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        int res = 0;
        if (l <= mid ) res = query(u << 1, l, r);
        if (r > mid) res += query(u << 1 | 1, l, r);
        return res;
    }
}
  • 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

扫描线

void pushup(int u)
{
    if (tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];
    else if (tr[u].l != tr[u].r)
    {
        tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
    }
    else tr[u].len = 0;
}

void modify(int u, int l, int r, int k)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].cnt += k;
        pushup(u);
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, k);
        if (r > mid) modify(u << 1 | 1, l, r, k);
        pushup(u);
    }
}
int main() {
    for (int i = 0, j = 0; i < n; i ++ )
    {
        double x1, y1, x2, y2;
        scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
        seg[j ++ ] = {x1, y1, y2, 1};
        seg[j ++ ] = {x2, y1, y2, -1};
        ys.push_back(y1), ys.push_back(y2);
    }

    sort(ys.begin(), ys.end());

    ys.erase(unique(ys.begin(), ys.end()), ys.end());

    build(1, 0, ys.size() - 2);

    sort(seg, seg + n * 2);

    double res = 0;
    for (int i = 0; i < n * 2; i ++ )
    {
        if (i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x);
        modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].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
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

分块

int id[N], len, tot;
struct node {
	int l, r;
	ll sum, tag;
}blk[N];
int n, m;

void update(int l, int r, ll val) {
	int idx = id[l], idy = id[r];
	if(idx == idy) {
		for(int i = l; i <= r; i ++)
			a[i] += val;
		blk[idx].sum += (r - l + 1) * val;
	} else {
		for(int i = l; i <= blk[idx].r; i ++)
			a[i] += val;
		blk[idx].sum += (blk[idx].r - l + 1) * val;
		for(int i = blk[idy].l; i <= r; i ++) 
			a[i] += val;
		blk[idy].sum += (r - blk[idy].l + 1) * val;
		for(int i = idx + 1; i < idy;  i ++) {
			blk[i].tag += val;
			blk[i].sum += val * (blk[i].r - blk[i].l + 1);
		}
	}
}

ll query(int l, int r) {
	int idx = id[l], idy = id[r];
	ll ans = 0;
	if(idx == idy) {
		for(int i = l; i <= r; i ++) {
			ans += a[i] + blk[idx].tag;
		}
		return ans;
	} else {
		for(int i = l; i <= blk[idx].r; i ++)
			ans += a[i] + blk[idx].tag;
		for(int i = blk[idy].l; i <= r; i ++) 
			ans += a[i] + blk[idy].tag;
		for(int i = idx + 1; i < idy;  i ++)
			ans += blk[i].sum;
		return ans;
	}
}

void build() {
	len = sqrt(n);
	for(int i = 1; i <= n; i ++) {
		id[i] = (i - 1) / len + 1;
		blk[id[i]].sum += a[i];
		if((i - 1) % len==0) blk[++tot].l = i;
		if(i%len == 0) blk[tot].r = i;
	}
    blk[tot].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

莫队

struct mo {
	int l, r;
	int id;
	bool operator < (const mo &o) const
	{
		if(l / len != o.l / len)
			return l < o.l;
		if(l / len & 1)
			return r < o.r;
		else return r > o.r;
	}
}q[N];
l = 1, r = 0, len = sqrt(n);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

带修莫队

// 增加时间戳
bool operator < (const mo &o) const
{
    if(l / len != o.l / len)
    return l < o.l;
    if(r / len != o.r / len)
    return r < o.r;
    return t < o.t;
}
// 进行单点修改后,交换原数组与操作子的值
while(now < q[i].t) {
    now ++; 
    if(upd[now].x >= l && upd[now].x <= r) {
        cnt[a[upd[now].x]] --;
        if(cnt[a[upd[now].x]] == 0) cur --;
        if(cnt[upd[now].y] == 0) cur ++;
        cnt[upd[now].y] ++;
    }
    swap(a[upd[now].x], upd[now].y);
}
while(now > q[i].t) {
    if(upd[now].x >= l && upd[now].x <= r) {
        cnt[a[upd[now].x]] --;
        if(cnt[a[upd[now].x]] == 0) cur --;
        if(cnt[upd[now].y] == 0) cur ++;
        cnt[upd[now].y] ++;
    }
    swap(a[upd[now].x], upd[now].y);
    now --;
}
  • 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

可持久化tire

int n, m;
int s[N];
int tr[M][2], max_id[M];
int root[N], idx;

void insert(int i, int k, int p, int q) {
    if(k < 0) {
        max_id[q] = i;
        return;
    }
    int v = s[i] >> k&1;
    if (p) tr[q][v ^ 1] = tr[p][v ^ 1];
    tr[q][v] = ++ idx;
    insert(i, k - 1, tr[p][v], tr[q][v]);
    max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]);
}

int query(int root, int C, int L) {
    int p = root;
    for (int i = 23; i >= 0; i --) {
        int v = C >> i & 1;
        if (max_id[tr[p][v ^ 1]] >= L) p = tr[p][v ^ 1];
        else p = tr[p][v];
    }
    return C ^ s[max_id[p]];
}

void tire_init() {
    max_id[0] = -1;
    root[0] = ++idx;
    insert(0, 23, 0, root[0]);
}

for(int i = 1; i <= n; i ++) {
        ll tmp;
        cin >> tmp;
        s[i] = s[i - 1] ^ tmp;
        root[i] = ++ idx;
        insert(i, 23, root[i - 1], root[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

字符串

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;
    }
}
  • 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

//AC自动机
#include <bits/stdc++.h>
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";
    }
}
  • 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

三.树上差分优化

    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;
    }

  • 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

后缀自动机

节点含义

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

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;
  • 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

hash

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 10;
const int base = 131;
using ull = unsigned long long;
char s[N];
ull h1[N], p[N], h2[N], ans = 0;
int n;

ull gh1(int l, int r) { 
    if(l > r) return 0;
    return h1[r] - h1[l - 1] * p[r - l + 1];
}
ull gh2(int l, int r) { 
    if(l > r) return 0;
    return h2[l] - h2[r + 1] * p[r - l + 1]; 
}

int main() {
    scanf("%s", s + 1); p[0] = 1;
    n = strlen(s + 1);
    for(int i = 1; i <= n; ++i) {
        h1[i] = h1[i - 1] * base + s[i];
        p[i] = p[i - 1] * base;
    }
    int f = 1;
    for(int i = n; i; i--) h2[i] = h2[i + 1] * base + s[i];
    for(int i = 1; i <= n; i ++) {
        if(s[i] != s[n - i + 1]) {
            f = 0;
            for(int j = i; j <= n - i + 1; j ++) {
                ull tmp1 = gh2(i, j);
                ull tmp2 = gh1(j + 1, n - i + 1);
                ull tmp3 = tmp1 * p[n - i + 1 - j] + tmp2;
                tmp1 = gh2(j + 1, n - i + 1);
                tmp2 = gh1(i, j);
                ull tmp4 = tmp1 * p[j - i + 1] + tmp2 ;
                if(tmp3 == tmp4) {
                    cout << i << " " << j << "\n";
                    return 0;
                }
            }
            for(int j = n - i + 1; j >= i; j --) {
                ull tmp2 = gh2(j, n - i + 1);
                ull tmp1;
                tmp1 = gh1(i, j - 1);
                ull tmp3 = tmp1 * p[n - i + 2 - j] + tmp2;
                tmp2 = gh2(i, j - 1);
                tmp1 = gh1(j, n - i + 1);
                ull tmp4 = tmp1 * p[j - i] + tmp2 ;
                if(tmp3 == tmp4) {
                    cout << j << " " << n - i + 1 << "\n";
                    return 0;
                }
            }
            break;
        }
    }
    if(f) {
        cout << 1 << " " << 1 << "\n";
    } else cout << -1 << " " << -1 << "\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

多项式

卷积

给定两个多项式 F ( x ) F(x) F(x) G ( x ) G(x) G(x),求解 F ( x ) ∗ G ( x ) F(x)*G(x) F(x)G(x),时间复杂度为 n l o g ( n ) nlog(n) nlog(n)

FFT

NTT

​ 只能求整数,并且模数必须是 q ∗ 2 k + 1 q*2^{k}+1 q2k+1的质数p, ( q , k ) (q,k) (q,k)可以取 ( 7 , 26 ) , ( 199 , 23 ) , ( 17 , 27 ) , ( 479 , 21 ) (7,26),(199,23),(17,27),(479,21) (7,26),(199,23),(17,27),(479,21)

constexpr ll mod = 998244353, g = 3;
LL power(LL a, LL r) {
    LL res = 1;
    for (; r; r >>= 1, a = a * a % mod) if (r & 1) res = res * a % mod;
    return res;
}

void DFT(LL* a, int n, bool inv = false) {
        static vector<int> r;
        if ((int)r.size() != n) {
            r.resize(n);
            for (int i = 0; i < n; i += 1) r[i] = i == 1 ? (n >> 1) : r[i >> 1] >> 1 | r[i & 1];
        }
        for (int i = 0; i < n; i += 1) if (i < r[i]) std::swap(a[i], a[r[i]]);
        for (int m = 1; m < n; m <<= 1) {
            LL m2 = m << 1, w0 = power(inv ? power(g, mod - 2) : g, (mod - 1) / m2);
            for (int i = 0; i < n; i += m2)
                for (int j = 0, w = 1; j < m; j += 1, w = w * w0 % mod) {
                    LL &x = a[m + i + j], &y = a[i + j], t = w * x % mod;
                    x = y - t; if (x < 0) x += mod;
                    y += t; if (y >= mod) y -= mod;
                }
        }
        if (inv) for (int i = 0, inv = power(n, mod - 2); i < n; i += 1) a[i] = a[i] * inv % mod;
    }
using POFF = vector<LL>;
POFF operator * (const POFF& f, const POFF& g){
    int n = f.size() + g.size() - 1, k = 1;
    while (k < n) k <<= 1;
    POFF h(k << 1);
    copy(f.begin(), f.end(), h.begin());
    copy(g.begin(), g.end(), h.begin() + k);
    DFT(h.data(), k);
    DFT(h.data() + k, k);
    for (int i = 0; i < k; i += 1) h[i] = h[i] * h[i + k] % mod;
    DFT(h.data(), k, true);
    h.resize(n);
    return h;
}
int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    POFF f(n), g(m);
    for (LL& x : f) cin >> x;
    for (LL& x : g) cin >> x;
    POFF h = f * g;
    for (LL x : h) cout << x << " ";
    return 0;
}

// from https://judge.yosupo.jp/submission/61121 Heltion
  • 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

Trick

∑ i = 1 n a i b i + k  翻转 a 后得到 ∑ i = 1 n a n − i + 1 b i + k ,因为 n − i + 1 + i + k = n + 1 + k \sum_{i=1}^na_ib_{i+k}\ 翻转a后得到\sum_{i=1}^na_{n-i+1}b_{i+k},因为n-i+1+i+k=n+1+k i=1naibi+k 翻转a后得到i=1nani+1bi+k,因为ni+1+i+k=n+1+k

​ 所以实际上等价于将数组a翻转后,将a倍长,然后求a和b的卷积,它们卷积的第 n + 1 n+1 n+1项到第 n 2 n^2 n2项就是答案。

组合数学

组合

可重集合的组合公式

​ 有k种元素,每个元素有ai,从中无序地选出r个元素,r <= ai,则有选法:
C k − 1 + r r C_{k-1+r}^{r} Ck1+rr

排列

可重集合的排列公式

​ 有k个元素,每个元素有ai个,不同排列的个数为
( ∑ i = 1 k a i ) ! ∏ i = 1 k ( a i ! ) \frac{(\sum_{i=1}^{k}a_i)!}{\prod_{i=1}^k(a_i!)} i=1k(ai!)(i=1kai)!

公式

∑ x = 0 m i n ( i , j ) C i x C j x = C i + j j \sum_{x=0}^{min(i,j)}C_{i}^{x}C{j}^{x}=C_{i+j}^{j} x=0min(i,j)CixCjx=Ci+jj

两个集合中各选出x个数,相当于从长度为i+j的数组中选出i个数

将长度为i+j的数组划分为前后两段,第一段i个数,第二段j个数

从中选取i个数,假设其中有x个数落到了第二段,有i−x个数落到了第一段。我们让第一段剩下x个数变成被选择的数,这样也就是 C i x C j x {}C_i^{x}C_j^{x}{} CixCjx

故容易得到原式子成立。

常见技巧

python

输入与输出

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

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

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

map

# 初始化
vis = {}
# 是否在vis内
vis.has_key(key)
# 以列表返回可遍历的(键, 值) 元组数组
vis.items()
# 以列表返回一个字典所有的键
vis.keys()
# 删除字典内所有元素
vis.clear()
# 删除字典给定键key对应的值 
vis.pop(key)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

trick

区间覆盖

​ 覆盖[1,m],给很多[l,r],必须两个区间有重合时才能合并成大区间。举例,要覆盖[1,5],现在有[1,3],[4,5],[3,5]必须选择[1,3],[3,5] 而不是[1,3], [4,5] ,因为后者两个区间没有重合部分。如何维护这种情形下,区间[1,m]是否全部被覆盖?对每个[l,r]使r–即可,只需要用线段树维护区间[1,m-1]的最小值是否大于等于1,添加一个区间就是让区间[l,r-1]加1。

交换变翻转

​ 对于一个01串,我们可以将交换两个字符,变成翻转两个字符各一次,保证翻转结束,1的数量不变,就可以等价成交换次数为翻转次数的一半。

绝对值

​ |a-b| = max(a-b, b-a)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/606713
推荐阅读
相关标签
  

闽ICP备14008679号