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

欧拉定理

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

​ 扩展欧几里得求逆元

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   ,   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\{\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

一些经典的反演

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

在求
∑ 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;
    }
}

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

高斯消元

异或高斯消元

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

直径

求树的直径并输出

两次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 << " ";

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;

最短路

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

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

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

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

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.最大匹配数 = 最小点覆盖 = 总点数 - 最大独立集 = 总点数 - 最小路径覆盖

匈牙利算法

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

拓扑排序

无向图求环

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)存在欧拉路径的充分必要条件:度数为奇数的点只能有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)存在欧拉路径的充分必要条件:要么所有点的出度均等于入度;要么除了两个点之外,其余所有点的出度等于入度,剩余的两个点:一个满足出度比入度多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];
}

左偏树

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

树状数组

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

线段树

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

扫描线

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

分块

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

莫队

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

带修莫队

// 增加时间戳
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 --;
}

可持久化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]);
   }

字符串

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

三.树上差分优化

    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;

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

多项式

卷积

给定两个多项式 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

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()]

map

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

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)

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
1 图论 3 1.1 术语 3 1.2 独立集、覆盖集、支配集之间关系 3 1.3 DFS 4 1.3.1 割顶 6 1.3.2 桥 7 1.3.3 强连通分量 7 1.4 最小点基 7 1.5 拓扑排序 7 1.6 欧拉路 8 1.7 哈密顿路(正确?) 9 1.8 Bellman-ford 9 1.9 差分约束系统(用bellman-ford解) 10 1.10 dag最短路径 10 1.11 二分图匹配 11 1.11.1 匈牙利算法 11 1.11.2 KM算法 12 1.12 网络流 15 1.12.1 最大流 15 1.12.2 上下界的网络的最大流 17 1.12.3 上下界的网络的最小流 17 1.12.4 最小费用最大流 18 1.12.5 上下界的网络的最小费用最小流 21 2 数论 21 2.1 最大公约数gcd 21 2.2 最小公倍数lcm 22 2.3 快速幂取模B^LmodP(O(logb)) 22 2.4 Fermat小定理 22 2.5 Rabin-Miller伪素数测试 22 2.6 Pollard-rho 22 2.7 扩展欧几里德算法extended-gcd 24 2.8 欧拉定理 24 2.9 线性同余方程ax≡b(mod n) 24 2.10 中国剩余定理 25 2.11 Discrete Logging(BL == N (mod P)) 26 2.12 N!最后一个不为0的数字 27 2.13 2^14以内的素数 27 3 数据结构 31 3.1 堆(最小堆) 31 3.1.1 删除最小值元素: 31 3.1.2 插入元素和向上调整: 32 3.1.3 堆的建立 32 3.2 并查集 32 3.3 树状数组 33 3.3.1 LOWBIT 33 3.3.2 修改a[p] 33 3.3.3 前缀和A[1]+…+A[p] 34 3.3.4 一个二维树状数组的程序 34 3.4 线段树 35 3.5 字符串 38 3.5.1 字符串哈希 38 3.5.2 KMP算法 40 4 计算几何 41 4.1 直线交点 41 4.2 判断线段相交 41 4.3 三点外接圆圆心 42 4.4 判断点在多边形内 43 4.5 两圆交面积 43 4.6 最小包围圆 44 4.7 经纬度坐标 46 4.8 凸包 46 5 Problem 48 5.1 RMQ-LCA 48 5.1.1 Range Minimum Query(RMQ) 49 5.1.2 Lowest Common Ancestor (LCA) 53 5.1.3 Reduction from LCA to RMQ 56 5.1.4 From RMQ to LCA 57 5.1.5 An<O(N), O(1)> algorithm for the restricted RMQ 60 5.1.6 An AC programme 61 5.2 最长公共子序列LCS 64 5.3 最长上升子序列/最长不下降子序列(LIS) 65 5.3.1 O(n^2) 65 5.3.2 O(nlogn) 66 5.4 Joseph问题 67 5.5 0/1背包问题 68 6 组合数学相关 69 6.1 The Number of the Same BST 69 6.2 排列生成 71 6.3 逆序 72 6.3.1 归并排序求逆序 72 7 数值分析 72 7.1 二分法 72 7.2 迭代法(x=f(x)) 73 7.3 牛顿迭代 74 7.4 数值积分 74 7.5 高斯消元 75 8 其它 77
1 图论 3 1.1 术语 3 1.2 独立集、覆盖集、支配集之间关系 3 1.3 DFS 4 1.3.1 割顶 6 1.3.2 桥 7 1.3.3 强连通分量 7 1.4 最小点基 7 1.5 拓扑排序 7 1.6 欧拉路 8 1.7 哈密顿路(正确?) 9 1.8 Bellman-ford 9 1.9 差分约束系统(用bellman-ford解) 10 1.10 dag最短路径 10 1.11 二分图匹配 11 1.11.1 匈牙利算法 11 1.11.2 KM算法 12 1.12 网络流 15 1.12.1 最大流 15 1.12.2 上下界的网络的最大流 17 1.12.3 上下界的网络的最小流 17 1.12.4 最小费用最大流 18 1.12.5 上下界的网络的最小费用最小流 21 2 数论 21 2.1 最大公约数gcd 21 2.2 最小公倍数lcm 22 2.3 快速幂取模B^LmodP(O(logb)) 22 2.4 Fermat小定理 22 2.5 Rabin-Miller伪素数测试 22 2.6 Pollard-rho 22 2.7 扩展欧几里德算法extended-gcd 24 2.8 欧拉定理 24 2.9 线性同余方程ax≡b(mod n) 24 2.10 中国剩余定理 25 2.11 Discrete Logging(BL == N (mod P)) 26 2.12 N!最后一个不为0的数字 27 2.13 2^14以内的素数 27 3 数据结构 31 3.1 堆(最小堆) 31 3.1.1 删除最小值元素: 31 3.1.2 插入元素和向上调整: 32 3.1.3 堆的建立 32 3.2 并查集 32 3.3 树状数组 33 3.3.1 LOWBIT 33 3.3.2 修改a[p] 33 3.3.3 前缀和A[1]+…+A[p] 34 3.3.4 一个二维树状数组的程序 34 3.4 线段树 35 3.5 字符串 38 3.5.1 字符串哈希 38 3.5.2 KMP算法 40 4 计算几何 41 4.1 直线交点 41 4.2 判断线段相交 41 4.3 三点外接圆圆心 42 4.4 判断点在多边形内 43 4.5 两圆交面积 43 4.6 最小包围圆 44 4.7 经纬度坐标 46 4.8 凸包 46 5 Problem 48 5.1 RMQ-LCA 48 5.1.1 Range Minimum Query(RMQ) 49 5.1.2 Lowest Common Ancestor (LCA) 53 5.1.3 Reduction from LCA to RMQ 56 5.1.4 From RMQ to LCA 57 5.1.5 An algorithm for the restricted RMQ 60 5.1.6 An AC programme 61 5.2 最长公共子序列LCS 64 5.3 最长上升子序列/最长不下降子序列(LIS) 65 5.3.1 O(n^2) 65 5.3.2 O(nlogn) 66 5.4 Joseph问题 67 5.5 0/1背包问题 68 6 组合数学相关 69 6.1 The Number of the Same BST 69 6.2 排列生成 71 6.3 逆序 72 6.3.1 归并排序求逆序 72 7 数值分析 72 7.1 二分法 72 7.2 迭代法(x=f(x)) 73 7.3 牛顿迭代 74 7.4 数值积分 74 7.5 高斯消元 75 8 其它 77
上海交通大学ACM模板是指为了方便上海交通大学ACM队伍在参加ACM国内外比赛时,准备的一份包含常用算法模板和数据结构实现的文件。这份模板ACM队伍日常训练和比赛中必备的工具和参考资料。 ACM模板通常包括多个文件,每个文件对应一个具体的算法或数据结构,并提供了相应算法的思想、伪代码和具体实现。常见的内容包括但不限于:搜索算法、图论算法、动态规划、字符串处理、数论算法、几何算法、数据结构等。 ACM模板的好处主要有以下几点: 1. 提高编程效率:ACM模板中的算法和数据结构已经经过了优化和测试,可以直接拿来使用,避免了从零开始编写代码的时间和精力消耗。 2. 加深理解:通过研究ACM模板中的算法和数据结构实现,可以更深入地了解算法的原理和应用场景,从而提升对ACM竞赛中常见问题的解决能力。 3. 快速调试:ACM比赛通常时间紧迫,要求快速解决问题。ACM模板可以提供一些已经调试通过的代码,可以直接用于ACM比赛中,减少调试的时间。 4. 统一编程习惯:ACM模板中的代码通常是经过一段时间的磨合和调试得到的,可以作为一个学习的范本,帮助学习者养成良好的编程习惯。 上海交通大学ACM模板是上海交通大学ACM队伍根据自身经验和成果整理而成的,旨在为队员提供便利和帮助。同时,ACM模板也可以随着时间的推移进行更新和完善,以适应新的算法和数据结构的发展和变化。 总的来说,上海交通大学ACM模板ACM竞赛中的宝贵资料,对于提升队伍的竞赛实力和解决问题的效率具有重要意义。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Krito.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值