ACM 模板库

Template For ACM

字符串

标准库

sscanf

sscanf(const char *__source, const char *__format, ...) :从字符串 __source 里读取变量,比如 sscanf(str,"%d",&a)

sprintf

sprintf(char *__stream, const char *__format, ...) :将 __format 字符串里的内容输出到 __stream 中,比如 sprintf(str,"%d",i)

strcmp

int strcmp(const char *str1, const char *str2):按照字典序比较 str1 str2str1 字典序小返回负值,一样返回 0,大返回正值 请注意,不要简单的认为只有 0, 1, -1 三种,在不同平台下的返回值都遵循正负,但并非都是 0, 1, -1

strcpy

char *strcpy(char *str, const char *src) : 把 src 中的字符复制到 str 中, str src 均为字符数组头指针,返回值为 str 包含空终止符号 '\0'

strncpy

char *strncpy(char *str, const char *src, int cnt) :复制至多 cnt 个字符到 str 中,若 src 终止而数量未达 cnt 则写入空字符到 str 直至写入总共 cnt 个字符。

strcat

char *strcat(char *str1, const char *str2) : 将 str2 接到 str1 的结尾,用 *str2 替换 str1 末尾的 '\0' 返回 str1

strstr

char *strstr(char *str1, const char *str2) :若 str2str1 的子串,则返回 str2str1 的首次出现的地址;如果 str2 不是 str1 的子串,则返回 NULL

strchr

char *strchr(const char *str, int c) :找到在字符串 str 中第一次出现字符 c 的位置,并返回这个位置的地址。如果未找到该字符则返回 NULL

strrchr

char *strrchr(const char *str, char c) :找到在字符串 str 中最后一次出现字符 c 的位置,并返回这个位置的地址。如果未找到该字符则返回 NULL

KMP

//以 1 为起点, 求nxt数组
nxt[1] = 0;
for(int i=2,j=0; i<=n; i++){
    while(j > 0 && a[i] != a[j+1]) j = nxt[j];
    if(a[i] == a[j+1]) j++;
    nxt[i] = j;
}

//求 f 匹配数组
for(int i=1,j=0;i<=m;i++){
    while(j > 0 && (j == n || b[i] != a[j+1])) j=nxt[j];
    if(b[i] == a[j+1]) j++;
    f[i] = j;
    //if(f[i] == n) 此时为A在B中的某一次出现
}

EXKMP

z[i] 是 s 和从 i 开始的 s 的后缀的最大公共前缀长度。

//初始下标为0
for(int i=1,l=0,r=0;i<n;i++){
    if(i <= r) z[i] = min(r-i+1, z[i-l]);
    while(i + z[i] < n && s[z[i]] == s[i + z[i]])++z[i];
    if(i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}

Manacher

const int N = 11000000 + 5;
char s[N], ma[N*2];
int mp[N*2];//mp[i] 为在s中以对应位置为中心的极大子回文串的总长度+1
int Manacher(char *s, int n){
    int len = 0;
    ma[len++] = '$'; ma[len++] = '#';
    for(int i=0;i<n;i++){
        ma[len++] = s[i];
        ma[len++] = '#';
    }
    ma[len] = 0;
    int mx = 0, id = 0;
    int res = 0;
    for(int i=0;i<len;i++){
        mp[i] = mx > i ? min(mp[2*id-i], mx - i) : 1;
        while(ma[i+mp[i]] == ma[i-mp[i]]) mp[i]++;
        if(i + mp[i] > mx) {
            mx = mp[i] + i;
            id = i;
        }
        res = max(res, mp[i] - 1);
    }
    return res;
}
int main(){
    scanf("%s", s);
    printf("%d", Manacher(s, strlen(s)));
    return 0;
}

AC自动机

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n;
char s[N];
namespace AC{
    int tr[N][26],tot;
    int e[N],fail[N];
    queue<int> q;
    void init(){
        for(int i=0;i<=tot;i++){
            memset(tr[i],0,sizeof tr[i]);
            fail[i] = e[i] = 0;
        }
        tot = 0;
    }
    void insert(char *s){
        int u = 0;
        int len = strlen(s + 1);
        for(int i=1;i<=len;i++){
            if(!tr[u][s[i] - 'a']){
                tr[u][s[i] - 'a'] = ++tot;
            }
            u = tr[u][s[i] - 'a'];
        }
        e[u] ++;
    }
    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;
    }
}
int main(){
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        AC::init();
        for(int i=1;i<=n;i++)scanf("%s",s+1),AC::insert(s);
        scanf("%s",s+1);
        AC::build();
        printf("%d\n",AC::query(s));
    }
    return 0;
}

后缀数组

1. 倍增

int wa[N],wb[N],wv[N],c[N], rk[N];
int sa[N], height[N];
int cmp(int *r,int a,int b,int l){
    return r[a] == r[b] && r[a + l] == r[b + l];
}
void build_sa(int *r,int * sa,int n,int m){
    int i,j,p,*x = wa,*y = wb, *t;
    for(i=0;i<m;i++)c[i] = 0;
    for(i=0;i<n;i++)c[x[i] = r[i]] ++;
    for(i=1;i<m;i++)c[i] += c[i-1];
    for(i=n-1;i>=0;i--)sa[--c[x[i]]] = i;
    for(j=1,p=1;p<n;j *= 2,m=p){
        for(p=0,i=n-j;i<n;i++) y[p++] = i;
        for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;
        for(i=0;i<n;i++)wv[i] = x[y[i]];
        for(i=0;i<m;i++)c[i] = 0;
        for(i=0;i<n;i++)c[wv[i]] ++;
        for(i=1;i<m;i++)c[i] += c[i-1];
        for(i=n-1;i>=0;i--)sa[--c[wv[i]]] = y[i];
        for(t = x,x = y,y = t,p = 1,x[sa[0]] = 0,i = 1;i<n;i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j) ? p-1 : p ++;
    }
}

2. DC3

#define F(x) ((x) / 3 + ((x) % 3 == 1 ? 0 : tb))
#define G(x) ((x) < tb ? (x) * 3 + 1 : ((x) - tb) * 3 + 2)
int n;
char s[N], t[N];
int a[N], wa[N], wb[N], c[N], wv[N], sa[N], be[N];
int rk[N], height[N];

int c0(int *r, int a, int b) {
    return r[a] == r[b] && r[a + 1] == r[b + 1] && r[a + 2] == r[b + 2];
}
int c12(int k, int *r, int a, int b) {
    if (k == 2)
        return r[a] < r[b] || r[a] == r[b] && c12(1, r, a + 1, b + 1);
    return r[a] < r[b] || r[a] == r[b] && wv[a + 1] < wv[b + 1];
}
void sort(int *r, int *a, int *b, int n, int m) {
    for (int i = 0; i < n; i++) wv[i] = r[a[i]];
    for (int i = 0; i < m; i++) c[i] = 0;
    for (int i = 0; i < n; i++) c[wv[i]]++;
    for (int i = 1; i < m; i++) c[i] += c[i - 1];
    for (int i = n - 1; i >= 0; i--) b[--c[wv[i]]] = a[i];
}
void dc3(int *r, int *sa, int n, int m) {
    int i, j, *rn = r + n, *san = sa + n, ta = 0, tb = (n + 1) / 3, tbc = 0, p;
    r[n] = r[n + 1] = 0;
    for (i = 0; i < n; i++) if (i % 3 != 0) wa[tbc++] = i;
    sort(r + 2, wa, wb, tbc, m);
    sort(r + 1, wb, wa, tbc, m);
    sort(r, wa, wb, tbc, m);
    for (p = 1, rn[F(wb[0])] = 0, i = 1; i < tbc; i++)
        rn[F(wb[i])] = c0(r, wb[i - 1], wb[i]) ? p - 1 : p++;
    if (p < tbc) dc3(rn, san, tbc, p);
    else for (i = 0; i < tbc; i++) san[rn[i]] = i;
    for (i = 0; i < tbc; i++) if (san[i] < tb) wb[ta++] = san[i] * 3;
    if (n % 3 == 1) wb[ta++] = n - 1;
    sort(r, wb, wa, ta, m);
    for (i = 0; i < tbc; i++) wv[wb[i] = G(san[i])] = i;
    for (i = 0, j = 0, p = 0; i < ta && j < tbc; p++)
        sa[p] = c12(wb[j] % 3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
    for (; i < ta; p++) sa[p] = wa[i++];
    for (; j < tbc; p++) sa[p] = wb[j++];
}

3. 求\(height\) 以及 \(lcp\)

void calheight(int *r, int * sa, int n){
    int i, j, k = 0;
    for (int i = 1; i <= n;i++)
        rk[sa[i]] = i;
    for (int i = 0; i < n;height[rk[i++]] = k){
        for (k ? k-- : 0, j = sa[rk[i] - 1]; r[i + k] == r[j + k];k++);
    }
}
struct RMQ{
    int mi[N][20], kk[N];
    void init(int n,int *a){
        kk[1] = 0;
        for (int i = 2; i <= n;i++)
            kk[i] = kk[i / 2] + 1;
        for (int i = 1; i <= n;i++)
            mi[i][0] = a[i];
        for (int j = 1; (1 << j) <= n;j++){
            for (int i = 1; i + (1 << j) - 1 <= n;i++){
                mi[i][j] = min(mi[i + (1 << (j - 1))][j - 1], mi[i][j - 1]);
            }
        }
    }
    int query(int l,int r){
        int k = kk[r - l + 1];
        return min(mi[l][k], mi[r - (1 << k) + 1][k]);
    }
} rmq;

后缀自动机

最多 \(2n\) 个点,\(3n\) 条边

const int N = 200000 + 5; //N为字符串长度两倍
const int P = 26;//P过大时改用unordered_map
char s[N], a[N];
struct node{
    int link, len, trans[P];
	void clear(){
        memset(trans,0, sizeof trans);
		link = len = 0;
	}
};
struct SAM{
    node S[N];
    int p, np, size;
    int b[N], c[N];
	SAM():p(1),np(1),size(1){}
	void clear(){
		for(int i=0;i<=size;i++)S[i].clear();
		np = size = p = 1;
	}
	void insert(char ch){
		int x = ch - 'a';
		np = ++size;
		S[np].len = S[p].len + 1;
		while(p != 0 && !S[p].trans[x]) S[p].trans[x] = np, p = S[p].link;
		if(p == 0)S[np].link = 1;
		else{
			int q, nq;
			q = S[p].trans[x];
			if(S[q].len == S[p].len + 1) S[np].link = q;
			else{
                nq = ++size;
				S[nq] = S[q]; 
                S[nq].len = S[p].len + 1;
				S[np].link = S[q].link = nq;
				while(p != 0 && S[p].trans[x] == q) S[p].trans[x] = nq, p = S[p].link;
			}
		}
		p = np;
	}
    // 基数排序,从size倒序遍历可以自底向上更新,多组数据时,c 数组要更新
    void sort(){
        for(int i=1;i<=size;i++)++c[S[i].len];
        for(int i=1;i<=size;i++)c[i] += c[i-1];
        for(int i=1;i<=size;i++)b[c[S[i].len]--] = i;
    }
}sam;

回文自动机

namespace PAT{
    const int SZ = 6e5+10;
    int ch[SZ][26],fail[SZ],cnt[SZ],len[SZ],tot,last;
    int be[SZ],ok[SZ];
    void init(int n){
        for(int i=0;i<=n+10;i++){
            fail[i] = cnt[i] = len[i] = 0;
            for(int j=0;j<26;j++)ch[i][j] = 0;
            be[i] = ok[i] = 0;
        }
        s[0] = -1;fail[0] = 1;last = 0;
        len[0] = 0;len[1] = -1;tot = 1;
    }
    inline int newnode(int x){
        len[++tot] = x;return tot;
    }
    inline int getfail(char *s, int x,int n){
        while(s[n-len[x]-1] != s[n])x = fail[x];
        return x;
    }
    void create(char *s){
        s[0] = -1;
        for(int i=1;s[i];++i){
            int t = s[i]- 'a';
            int p = getfail(s, last,i);
            if(!ch[p][t]){
                int q = newnode(len[p]+2);
                fail[q] = ch[getfail(s, fail[p],i)][t];
                ch[p][t] = q;
            }
            ++cnt[last = ch[p][t]];
        }
    }
    void solve(){
        for(int i=tot;i>=2;i--){
            if(be[i] == 0)be[i] = i;
            while(be[i] >= 2 && len[be[i]] > (len[i] + 1)/2)be[i] = fail[be[i]];
            if(len[be[i]] == (len[i]+1)/2)ok[i] = 1;
            be[fail[i]] = be[i];
        }
        for(int i=tot;i>=2;i--){
            cnt[fail[i]] += cnt[i];
            if(ok[i]) res[len[i]] += cnt[i];
        }
    }
}

数论

矩阵快速幂

struct matrix{
    int r, c;
    ll s[N][N];
    matrix(int r=0,int c=0):r(r),c(c){
        memset(s, 0, sizeof s);
    }
};
matrix operator*(const matrix&a, const matrix&b){
    matrix c = matrix(a.r, b.c);
    for (int i = 0; i < c.r;i++){
        for (int j = 0; j < c.c;j++)
            for (int k = 0; k < a.c;k++)
                c.s[i][j] += a.s[i][k] * b.s[k][j];
    }
    return c;
}
matrix power(matrix a,int b){
    matrix res = a;
    b--;
    for (; b;b>>=1){
        if(b & 1)
            res = res * a;
        a = a * a;
    }
    return res;
}

数据结构

线段树合并

const int N = 200000 + 5;
int head[N], ver[N<<1], nxt[N<<1], tot;
int dep[N], f[N][20];
int n, m;
void add(int x, int y){
    ver[++tot] = y, nxt[tot] = head[x], head[x] = tot;
}
void dfs(int x, int fa){
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(y == fa) continue;
        f[y][0] = x;
        dep[y] = dep[x] + 1;
        dfs(y, x);
    }
}
int lca(int x, int y){
    if(dep[x] > dep[y]) swap(x, y);
    for(int i=19;i>=0;i--) if(dep[f[y][i]] >= dep[x]) y = f[y][i];
    if(x == y) return x;
    for(int i=19;i>=0;i--) if(f[y][i] != f[x][i]) y = f[y][i], x = f[x][i];
    return f[x][0];
}
struct SegTree{
    int l, r;
    int mx, pos;
}t[N*40];
int rt[N], cnt;
int res[N], st[N*40], top;
int newnode(){
    if(top) return st[top--];
    return ++cnt;
}
void rubbish(int p){
    st[++top] = p;
    t[p].l = t[p].r = t[p].mx = t[p].pos = 0;
}
void pushup(int p){
    int ls = t[p].l, rs = t[p].r;
    if(t[ls].mx >= t[rs].mx){
        t[p].mx = t[ls].mx;
        t[p].pos = t[ls].pos;
    } else {
        t[p].mx = t[rs].mx;
        t[p].pos = t[rs].pos;
    }
}
void change(int &p, int l, int r, int pos, int val){
    if(!p) p = newnode();
    if(l == r){
        t[p].mx += val;
        if(t[p].mx) t[p].pos = l;
        else t[p].pos = 0;
        return ;
    }
    int mid = l + r >> 1;
    if(pos <= mid) change(t[p].l, l, mid, pos, val);
    else change(t[p].r, mid+1, r, pos, val);
    pushup(p);
}
int merge(int u, int v, int l, int r){
    if(!u) return v;
    if(!v) return u;
    int w = newnode();
    if(l == r){
        t[w].mx = t[u].mx + t[v].mx;
        if(t[w].mx)t[w].pos = l;
        return w;
    }
    int mid = l + r >> 1;
    t[w].l = merge(t[u].l, t[v].l, l, mid);
    t[w].r = merge(t[u].r, t[v].r, mid + 1, r);
    pushup(w);
    rubbish(u);rubbish(v);
    return w;
}
void get(int x, int fa){
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(y == fa) continue;
        get(y, x);
        rt[x] = merge(rt[x], rt[y], 1, 1e5);
    }
    res[x] = t[rt[x]].pos;
}
int main(){
    scanf("%d%d", &n, &m);
    for(int i=1;i<n;i++){
        int x, y;scanf("%d%d", &x, &y);
        add(x, y);
        add(y, x);
    }
    dep[1] = 1;
    dfs(1, 0);
    for(int j=1;j<20;j++)for(int i=1;i<=n;i++) f[i][j] = f[f[i][j-1]][j-1];
    while(m--){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        int t = lca(x, y);
        if(t != x && t != y){
            change(rt[x], 1, 1e5, z, 1);
            change(rt[y], 1, 1e5, z, 1);
            change(rt[t], 1, 1e5, z, -1);
            change(rt[f[t][0]], 1, 1e5, z, -1);
        }else if(t == x){
            change(rt[y], 1, 1e5, z, 1);
            change(rt[f[t][0]], 1, 1e5, z, -1);
        }else if(t == y){
            change(rt[x], 1, 1e5, z, 1);
            change(rt[f[t][0]], 1, 1e5, z, -1);
        }
    }
    get(1, 0);
    for(int i=1;i<=n;i++) printf("%d\n", res[i]);
    return 0;
}

图论

最短路

1. Dijkstra

\(n^2\)

const int N = 3010 + 5;
int a[N][N], d[N], n, m;
bool v[N];
void dijkstra(){
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    d[1] = 0;
    for(int i=1;i<n;i++){ // 重复进行 n-1 次
        int x = 0;
        for(int j=1;j<=n;j++){
            if(!v[j] && (x == 0 || d[j] < d[x])) x = j;
        }
        v[x] = 1;
        for(int y = 1; y <= n; y++)
            d[y] = min(d[y], d[x] + a[x][y]);
    }
}

\((m+n)\log n\)

const int N = 100010;
const int M = 1000010;
int head[N], ver[M], edge[M], nxt[M], d[N];
bool v[N];
int n, m, tot, s;
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>, greater<pii> >q;
void add(int x, int y, int z){ver[++tot] = y,edge[tot] = z, nxt[tot]=head[x],head[x] = tot; }
void dijkstra(){
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    d[1] = 0;
    q.push(make_pair(0,1));
    while(q.size()){
        int x = q.top().second;q.pop();
        if(v[x])continue;
        v[x] = 1;
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            if(d[y] > d[x] + edge[i]){
                d[y] = d[x] + edge[i];
                q.push({d[y], y});
            }
        }
    }
}
int main() {
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1,x,y,z;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);//此模板为有向图
    }
    dijkstra();
    for(int i=1;i<=n;i++)printf("%d ",d[i]);
    return 0;
}

2. SPFA

\(O(NM)\)

const int N = 100000 + 5;
const int M = 1000010;
int head[N], ver[M], edge[M], nxt[M], d[N];
bool v[N];
int n, m, tot, s;
queue<int> q;
void add(int x, int y, int z){ver[++tot] = y,edge[tot] = z, nxt[tot]=head[x],head[x] = tot; }
void spfa(){
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    d[1] = 0, v[1] = 1;
    q.push(1);
    while(q.size()){
        int x = q.front();q.pop();
        v[x] = 0;
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            if(d[y] > d[x] + edge[i]){
                d[y] = d[x] + edge[i];
                if(!v[y]) q.push(y), v[y] = 1;
            }
        }
    }
}

int main() {
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1,x,y,z;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);//此模板为有向图
    }
    spfa();
    for(int i=1;i<=n;i++)printf("%d ",d[i]);
    return 0;
}

3. Floyd

\(O(n^3)\) 可以用来求传递闭包

void 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] = min(d[i][j], d[i][k] + d[k][j]);
}

Johnson

利用势能与路径无关,至于起点和终点有关的性质,将负权边转换为正权边,进而利用dijkstra堆优化快速求解最短路

const int N = 3000 + 5;
const int M = 20000 + 5;
int n,m;
int head[N], ver[M], nxt[M], edge[M], tot;
int h[N],d[N],v[N],cnt[N];
priority_queue<pair<int,int> > q;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
bool spfa(){
    memset(h, 0x3f, sizeof h);
    memset(v, 0, sizeof v);
    queue<int> q;
    q.push(0);
    h[0] = 0;
    v[0] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        v[x] = 0;
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            if(h[y] > h[x] + edge[i]){
                h[y] = h[x] + edge[i];
                cnt[y] = cnt[x] + 1;
                if(cnt[y] >= n) return false;
                if(!v[y]) q.push(y), v[y] = 1;
            }
        }
    }
    return true;
}
void dijkstra(int s){
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    q.push({0, s});d[s] = 0;
    while(q.size()){
        int x = q.top().second;q.pop();
        if(v[x])continue;
        v[x] = 1;
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            if(d[y] > d[x] + edge[i]){
                d[y] = d[x] + edge[i];
                q.push({-d[y],y});
            }
        }
    }
    ll res = 0;
    for(int i=1;i<=n;i++){
        if(d[i] == inf) res += 1ll * i * 1e9;
        else {
            // d[i] = f[i] + h[s] - h[i];
            d[i] -= h[s] - h[i];
            res += 1ll * i * d[i];
        }
    }
    printf("%lld\n",res);
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1,x,y,z;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
    }
    for(int i=1;i<=n;i++) add(0,i,0);
    if(!spfa()){
        puts("-1");
        return 0;
    }
    for(int x=1;x<=n;x++){
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            edge[i] = edge[i] + h[x] - h[y];
        }
    }
    for(int x=1;x<=n;x++){
        dijkstra(x);
    }
    return 0;
}

最小生成树

1. Kruskal

\(O(m\log m)\) 当边权比较小时可以直接桶排,降低复杂度(2019上海区域赛E)

struct rec{int x,y,z;} edge[500010];
int fa[100010], n, m, ans;
bool operator < (rec a, rec b){
    return a.z < b.z;
}
int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
    sort(edge+1,edge+m+1);
    for(int i=1;i<=n;i++) fa[i] = i;
    for(int i=1;i<=m;i++){
        int x = find(edge[i].x);
        int y = find(edge[i].y);
        if(x == y)continue;
        fa[x] = y;
        ans += edge[i].z;
    }
    cout << ans << endl;
    return 0;
}

2. Prim

\(O(n^2)\)

const int N = 3000 + 5;
int a[N][N], d[N], n, m, ans;
bool v[N];
void prim(){
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    d[1] = 0;
    for(int i=1;i < n;i++){
        int x = 0;
        for(int j=1;j<=n;j++)
            if(!v[j] && (x == 0 || d[j] < d[x])) x = j;
        v[x] = 1;
        for(int y=1;y<=n;y++)
            if(!v[y])d[y] = min(d[y], a[x][y]);
    }
}
int main() {
    cin >> n >> m;
    memset(a, 0x3f, sizeof a);
    for(int i=1;i<=n;i++) a[i][i] = 0;
    for(int i=1;i<=m;i++){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        a[y][x] = a[x][y] = min(a[x][y], z);
    }   
    prim();
    for(int i=2;i<=n;i++) ans += d[i];
    cout << ans << endl;
    return 0;
}

\(O(m\log n)\)

const int N = 400010;
int n,m,st,ed;
int head[N],ver[N],nxt[N],edge[N],tot;
int d[N],v[N];
inline void add(int x,int y,int z){
    ver[++tot] = y;edge[tot] = z;nxt[tot] = head[x];head[x] = tot;
}
int x,y,z;
int main() 
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    memset(d,0x3f,sizeof d);
    priority_queue<pair<int,int> > q;
    q.push({0,1});d[1] = 0;
    ll res = 0;
    while(q.size()){
        int x = q.top().second;q.pop();
        if(v[x])continue;
        res += d[x];
        v[x] = 1;
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            if(d[y] > edge[i]){
                d[y] = edge[i];
                q.push({-d[y],y});
            }
        }
    }
    cout << res << endl;
    return 0;
}

最小斯坦纳树

给定一个包含 \(n\) 个结点和 \(m\) 条带权边的无向连通图 \(G=(V,E)\)
再给定包含 \(k\) 个结点的点集 \(S\),选出 \(G\) 的子图 \(G'=(V',E')\) 使得:

  1. \(S\subseteq V'\)
  2. \(G′\) 为连通图;
  3. \(E′\) 中所有边的权值和最小。
    你只需要求出 \(E′\)中所有边的权值和。

\(dp[i][s]\) 表示 以 i 为根的一棵树,包含集合 S 中所有点的最小代价

  1. \(w[i][j] + dp[j][s] -> dp[i][s]\)
  2. \(dp[i][T] + dp[i][S-T] -> dp[i][S]\quad(T \subseteq S)\)

第二类转移可以用枚举子集来转移,第一类转移是一个三角不等式,可以用spfa或者dijkstra

const int N = 100 + 5;
const int M = 1010;
int n, m, k, x, y, z, tot;
int head[N], ver[M], nxt[M], edge[M], dp[N][4200];
int p[N], vis[N];
priority_queue<pair<int,int>> q;
void add(int x, int y, int z){
    ver[++tot] = y; edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
void dijkstra(int s){
    memset(vis, 0, sizeof vis);
    while(q.size()){
        auto t = q.top();q.pop();
        int x = t.second;
        if(vis[x]) continue;
        vis[x] = 1;
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            if(dp[y][s] > dp[x][s] + edge[i]){
                dp[y][s] = dp[x][s] + edge[i];
                q.push(make_pair(-dp[y][s], y));
            }
        }
    }
}
int main(){
    scanf("%d%d%d", &n, &m, &k);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);add(y, x, z);
    }
    memset(dp, 0x3f, sizeof dp);
    for(int i=1;i<=k;i++){
        scanf("%d", &p[i]);
        dp[p[i]][1<<(i-1)] = 0;
    }
    for(int s = 1; s < (1 << k); s++){
        for(int i=1; i <= n; i++){
            for(int subs = (s-1)&s; subs; subs = s & (subs-1)){
                dp[i][s] = min(dp[i][s], dp[i][subs]+dp[i][s^subs]);
            }
            if(dp[i][s]!=inf) q.push(make_pair(-dp[i][s], i));
        }
        dijkstra(s);
    }
    printf("%d\n", dp[p[1]][(1<<k)-1]);

    return 0;
}

树的直径

树形dp

void dp(int x){
    v[x] = 1;
    for(int i=head[x];i;i=nxt[i]){
		int y = ver[i];
        if(v[y])continue;
        dp(y);
        ans = max(ans, d[x] + d[y] + edge[i]);
        d[x] = max(d[x], d[y], edge[i]);
    }
}

两次dfs或bfs,bfs可以记录路径

const int N = 200010;
vector<pair<int,int> > v[N];
int n, pre[N], vis[N],mark[N];
ll d[N];
int bfs(int s){
    queue<int> q;
    q.push(s);
    memset(vis, 0, sizeof vis);
    memset(d, 0, sizeof d);
    memset(pre, 0, sizeof pre);
    vis[s] = d[s] = 0;
    int t = s;
    while(q.size()){
        int x = q.front();
        q.pop();
        if(d[x] > d[t])***
            t = x;
        vis[x] = 1;
        for (int i = 0; i < v[x].size();i++){
            int y = v[x][i].first;
            if(vis[y])
                continue;
            d[y] = d[x] + v[x][i].second;
            pre[y] = x;****
            q.push(y);
        }
    }
    return t;
}
int main(){
    scanf("%d", &n);
    for (int i = 1,x, y, z; i < n;i++){
        scanf("%d%d%d", &x, &y, &z);
        v[x].push_back({y, z});
        v[y].push_back({x, z});
    }
    int s = bfs(1);
    int t = bfs(s);
    int p = t, np = pre[t];
    ll len = d[t];
    cout << len << endl;
    while(p != s){
        mark[p] = 1;
        p = pre[p];
        mark[p] = 1;
    }
    p = t;
    return 0;
}

树上启发式合并

求每个子树上不同颜色的个数

// U41492
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100000 + 5;
int c[N], cou[N], res[N], son[N];
int dfn[N], cid[N],sz[N];
int cnt, num;
vector<int> v[N];
int n,m;
void dfs(int x, int fa){
    dfn[x] = ++cnt, cid[cnt] = x;
    sz[x] = 1;
    for(int i=0;i<v[x].size();i++){
        int y = v[x][i];
        if(y == fa)continue;
        dfs(y,x);
        sz[x] += sz[y];
        if(son[x] == 0 || sz[y] > sz[son[x]]) son[x] = y;
    }   
}
//核心思想即所有重边只会走一次,
void get(int x, int fa, int flag){
    for(int i=0;i<v[x].size();i++){
        int y = v[x][i];
        if(y == fa || y == son[x])continue;//不保存答案遍历时,一定不能遍历重儿子
        get(y, x, 0);
    }
    if(son[x]) get(son[x], x, 1);
    for(int i=0;i<v[x].size();i++){
        int y = v[x][i];
        if(y == fa || y == son[x])continue;
        for(int j=dfn[y];j<=dfn[y]+sz[y]-1;j++){
            if(++cou[c[cid[j]]] == 1) num++;
        }
    }
    if(++cou[c[x]] == 1)num++;
    res[x] = num;
    if(flag == 0){
        for(int j=dfn[x];j<=dfn[x]+sz[x]-1;j++){
            if(--cou[c[cid[j]]] == 0) num--;
        }
    }
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int x,y;scanf("%d%d",&x,&y);
        v[x].push_back(y);
        v[y].push_back(x);
    }
    for(int i=1;i<=n;i++)scanf("%d",&c[i]);
    dfs(1, 0);
    get(1, 0, 0);
    scanf("%d",&m);
    while(m--){
        int x;scanf("%d",&x);
        printf("%d\n",res[x]);
    }
    return 0;
}

点分治

求是否存在路径权值和为k的路径

#include <stdio.h>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <string>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <ctime>
#include <vector>
#include <fstream>
#include <list>
#include <iomanip>
#include <numeric>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(s) memset(s, 0, sizeof(s))
const int inf = 0x3f3f3f3f;
const int N = 10010,M = 20010;
int head[N],ver[M],nxt[M],tot;
int del[N],sz[N],st[N], q[N], top;
int n,m, core, min_height, cnt, all;
int query[N];
int dis[N],k,edge[M];
bool has[10000010], ret[N];
void add(int x,int y,int z){
    ver[++tot] = y;edge[tot] = z;nxt[tot]=head[x];head[x] = tot;
}
void getsz(int x, int fa){
    sz[x] = 1;
    int mx = 0;
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(y == fa || del[y]) continue;
        getsz(y, x);
        mx = max(mx, sz[y]);
        sz[x] += sz[y];
    }
    mx = max(mx, all - sz[x]);
    if(mx < min_height) min_height = mx, core = x;
}
void getdis(int x, int fa){
    if(dis[x]<=10000000) //没用的就不要存到里面
    st[++cnt] = dis[x],q[++top] = dis[x];
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(y == fa || del[y])continue;
        dis[y] = dis[x] + edge[i];
        getdis(y, x);
    }
}
void get(int x, int fa){
    del[x] = 1;
    top = cnt = 0;
    has[0] = true;
    q[++top] = 0; // 添加到历史集合中
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(y == fa || del[y])continue;
        cnt = 0;
        dis[y] = edge[i];
        getdis(y, fa);
        for(int k=1;k<=cnt;k++){
            for(int j=1;j<=m;j++){
                if(query[j] >= st[k]) ret[j] |= has[query[j] - st[k]];
            }
        }
        for(int k=1;k<=cnt;k++) has[st[k]] = 1;
    }
    for(int i=1;i<=top;i++) has[q[i]] = false;
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(y == fa || del[y])continue;
        min_height = inf;
        core = 0;
        all = sz[y];
        getsz(y, x);
        getsz(core, 0);
        get(core, x);
    }
}
int main() 
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,z);
    }
    for(int i=1;i<=m;i++)scanf("%d",&query[i]);
    core = 0;min_height = inf;
    getsz(1, 0);
    getsz(core, 0);
    get(core, 0);
    for(int i=1;i<=m;i++){
        if(ret[i]) puts("AYE");
        else puts("NAY");
    }
    return 0;
}

最近公共祖先

1. 倍增

onst int SZ = 50010;
int f[SZ][20],d[SZ],dist[SZ];//开足空间
int ver[2*SZ],nxt[2*SZ],edge[2*SZ],head[2*SZ];
int T,n,m,tot,t;
queue<int> q;
void add(int x,int y,int z){
    ver[++tot] = y;
    edge[tot] = z;
    nxt[tot] = head[x];
    head[x] = tot;
}
void bfs(){ // dfs或者bfs都可以,d[1]一定要为1
    q.push(1);d[1] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            int y = ver[i];
            if(d[y])continue;
            d[y] = d[x] + 1;
            dist[y] = dist[x] + edge[i];
            f[y][0] = x;
            for(int j=1;j<=t;j++)
                f[y][j] = f[f[y][j-1]][j-1];
            q.push(y);
        }
    }
}
int lca(int x,int y){
    if(d[x] > d[y])swap(x,y);//x在y上面
    for(int i=t;i>=0;i--){
        if(d[f[y][i]] >= d[x]) y = f[y][i];//不断往上面调整
    }
    if(x == y)return x;
    for(int i=t;i>=0;i--){
        if(f[x][i] != f[y][i]) x = f[x][i],y = f[y][i];
    }
    return f[x][0];
}

3. Tarjan

线性离线,不太常用,但离线,结合并查集的思想值得借鉴

#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int ver[2*N],nxt[2*N],edge[2*N],head[N];
int fa[N],d[N],v[N],lca[N],ans[N];
vector<int> query[N],query_id[N];
int T,n,m,tot,t;
void add(int x,int y,int z){
    ver[++tot] = y;edge[tot] = z;nxt[tot] = head[x];head[x] = tot;
}
void add_query(int x,int y,int id){
    query[x].push_back(y);query_id[x].push_back(id);
    query[y].push_back(x);query_id[y].push_back(id);
}
int find(int x){
    return x==fa[x]?x:fa[x] = find(fa[x]);
}
void tarjan(int x){
    v[x] = 1;
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(v[y])continue;
        d[y] = d[x] + edge[i];
        tarjan(y);
        fa[y] = x;
    }
    for(int i=0;i<query[x].size();i++){
        int y = query[x][i],id = query_id[x][i];
        if(v[y] == 2){
            int lca = find(y);
            ans[id] = min(ans[id],d[x] + d[y] - 2*d[lca]);
        }
    }
    v[x] = 2;
}
int main(){
    cin>>T;
    while(T--){
        cin>>n>>m;
        for(int i=1;i<=n;i++){
            head[i] = 0;fa[i] = i;v[i] = 0;
            query[i].clear();query_id[i].clear();
        }
        tot = 0;
        for(int i=1;i<n;i++){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);add(y,x,z);
        }
        for(int i=1;i<=m;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            if(x == y)ans[i] = 0;
            else{
                add_query(x,y,i);
                ans[i] = 1 << 30;
            }
        }
        tarjan(1);
        for(int i=1;i<=m;i++)
            printf("%d\n",ans[i]);
    }
    return 0;
}

无向图连通性

求割边

无向边 \((x,y)\) 是桥,当且仅当搜索树上存在 \(x\) 的一个子节点 \(y\) ,满足:

\[dfn[x] < low[y] \]

void tarjan(int x, int in_edge){
 	dfn[x] = low[x] = ++num;
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(!dfn[y]){
            tarjan(y,i);
            low[x] = min(low[x], low[y]);
            if(low[y] > dfn[x])
                bridge[i] = bridge[i ^ 1] = true; //标记为割边
        } else if (i != (in_edge ^ 1)) {
            low[x] = min(low[x], dfn[y]);
        }
    }
}
//main函数中
//加边要保证第一条边编号为2
for(int i=1;i<=n;i++)
	if(!dfn[i])tarjan(i, 0);

割点

\(x\) 不是搜索树的根节点(\(dfs\)) ,则 \(x\) 是割点当且仅当搜索树上存在 \(x\) 的一个子节点\(y\) 满足:$$dfn[x] \le low[y]$$

void tarjan(int x){
    dfn[x] = low[x] = ++num;
    int flag = 0;
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(!dfn[y]){
            tarjan(y);
            low[x] = min(low[x], low[y]);
            if(low[y] >= dfn[x]){
                flag ++;
                if(x != root || flag >= 2) cut[x] = true;
            }
        } else low[x] = min(low[x], dfn[y]);
    }
}
for(int i=1;i<=n;i++)
    if(!dfn[i])tarjan(i);

边双连通分量 (e-DCC)

只需要求出无向图的所有的桥,把桥都删除后,无向图会分成若干个连通块,每个连通块就是一个“边联通分量”。

int c[N], dcc;
void dfs(int x){
    c[x] = dcc;// x所属的连通块编号
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(c[y] || bridge[i])continue; // 不访问桥
        dfs(y);
    }
}
//main函数中
for(int i=1;i<=n;i++)if(!c[i]){
    ++dcc;
    dfs(i);
}

e-DCC 的缩点
把每个\(e-DCC\) 看作一个节点,把桥\((x,y)\) 看作连接编号为\(c[x]\)\(c[y]\)\(e-DCC\) 对应节点的无向边,会产生一棵树(若原来的图不连通,则产生森林)。

int hc[N], vc[N*2], nc[N*2], tc;
void add_c(int x,int y){
    vc[++tc] = y, nc[tc] = hc[x], hc[x] = tc;
}
//main函数中
tc = 1;
for(int i=2;i<=tot;i++){
    int x = ver[i ^ 1], y = ver[i];
    if(c[x] == c[y])continue;
    add_c(c[x],c[y]);
}

点双连通分量(v-DCC) 求法

点双连通分量并不是指“删除割点后图中剩余的连通块”

割点可能属于多个\(v-DCC\)

void tarjan(int x){
    dfn[x] = low[x] = ++num;
    st[++top] = x;
    if(x == root && head[x] == 0){ // 孤立点特判
        dcc[++cnt].push_back(x);
        return ;
    }
    int flag = 0;
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(!dfn[y]){
            tarjan(y);
            low[x] = min(low[x], low[y]);
            if(low[y] >= dfn[x]){ //割点成立条件满足
                flag ++;
                if(x != root || flag > 1)cut[x] = true;
                cnt++;
                int z;
                do{
                    z = st[top--];
                    dcc[cnt].push_back(z);
                }while(z != y);//栈中直到 y 都出栈
                dcc[cnt].push_back(x);
            }
        }else low[x] = min(low[x], dfn[y]);
    }
}

v-DCC 的缩点
设图中共有 \(p\) 个割点和 \(t\)\(v-DCC\)。我们建立一张包含\(p+t\) 个节点的新图,把每个\(v-DCC\) 和每个割点作为新图中的节点,并在每个割点与包含它的所有\(v-DCC\) 之间连边。容易发现,这张新图其实是一颗树(或森林)

//给每个割点一个新的编号(编号从cnt+1开始)
num = cnt;
for(int i=1;i<=n;i++)
    if(cut[i])new_id[i] = ++num;
tc = 1;
for(int i=1;i<=cnt;i++){
    for(int j=0;j<dcc[i].size();j++){
        int x = dcc[i][j];
        if(cut[x]){
            add_c(i,new_id[x]);
            add_c(new_id[x],i);
        }else c[x] = i; // 除割点外,其他点仅属于1个v-DCC
    }
}

有向图连通性

求强连通分量

const int N = 100010, M = 1000010;
int head[N], ver[M], nxt[M], dfn[N], low[N];
int st[N], ins[N], c[N];
vector<int> scc[N];
int n, m, tot, num, top, cnt;
void add(int x, int y){
    ver[++tot] = y, nxt[tot] = head[x], head[x] = cnt;
}
void tarjan(int x){
    dfn[x] = low[x] = ++num;
    st[++top] = x, ins[x] = 1;
    for(int i=head[x];i;i=nxt[i]){
        if(!dfn[ver[i]]){
            tarjan(ver[i]);
            low[x] = min(low[x], low[ver[i]]);
        }else if(ins[ver[i]]) //有作用的横叉边以及返祖边
            low[x] = min(low[x], dfn[ver[i]]);
    }
    if(dfn[x] == low[x]){//x可以作为这个连通分量的入口
        cnt ++;int y;
        do{
            y = st[top--], ins[y] = 0;
            c[y] = cnt, scc[cnt].push_back(y);
        }while(x != y);
    }
}

缩点

for(int x=1;x<=n;x++){
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(c[x] == c[y])continue;
        add_c(c[x],c[y]);
    }
}

欧拉路

void enlur(){
 	st[++top] = 1;
    while(top > 0){
        int x = st[top] , i = head[x];
        while(i && vis[i]) i = nxt[i];
        if(i){
            st[++top] = ver[i];
            vis[i] = vis[i ^ 1] = 1;
            head[x] = nxt[i]; // 会修改head[x] 的值
        }else{
            top --;
            ans[++t] = x;
        }
    }
}

二分图

1. 二分图判定

染色法

int n;
int head[N],edge[M],nxt[M],tot;
int color[N];
bool dfs(int u,int fa,int c){
    co[u] = c;
    for(int i=head[u];i;i=nxt[i]){
        int j = edge[i];
        if(co[j] == -1)
            if(!dfs(j,u,!c))return false;
        else if(co[j] == c)return false;
    }
    return true;
}
bool check(){
    memset(co,-1,sizeof co);
    bool flag = true;
    for(int i=1;i<=n;i++){
        if(co[i] == -1){
            if(!dfs(i,-1,0)){
                flag = false;
                break;
            }
        }
    }
    return flag;
}

2. 二分图最大匹配

匈牙利算法 复杂度 \(O(NM)\)

bool dfs(int x){
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(!vis[y]){
            vis[y] = 1;
            if(!match[y] || dfs(y)){
                match[y] = x; return true;
            }
        }
    }
    return false;
}
for(int i=1;i<=n;i++){
    memset(vis, 0, sizeof vis);
    if(dfs(i)) res++;
}

3. 二分图带权最大匹配

KM \(O(n^3)\)

2019南京 J spy

ll n, a[N],b[N],c[N],p[N];
ll w[N][N];
ll lx[N] , ly[N];
ll match[N];
ll slack[N];
bool vy[N];
ll pre[N];
void bfs( ll k ){
    ll x , y = 0 , yy = 0 , delta;
    memset( pre , 0 , sizeof(pre) );
    for( ll i = 1 ; i <= n ; i++ ) slack[i] = inf;
    match[y] = k;
    while(1){
        x = match[y]; delta = inf; vy[y] = true;
        for( ll i = 1 ; i <= n ;i++ ){
            if( !vy[i] ){
                if( slack[i] > lx[x] + ly[i] - w[x][i] ){
                    slack[i] = lx[x] + ly[i] - w[x][i];
                    pre[i] = y;
                }
                if( slack[i] < delta ) delta = slack[i] , yy = i ;
            }
        }
        for( ll i = 0 ; i <= n ; i++ ){
            if( vy[i] ) lx[match[i]] -= delta , ly[i] += delta;
            else slack[i] -= delta;
        }
        y = yy ;
        if( match[y] == -1 ) break;
    }
    while( y ) match[y] = match[pre[y]] , y = pre[y];
}
 
ll KM(){
    memset( lx , 0 ,sizeof(lx) );
    memset( ly , 0 ,sizeof(ly) );
    memset( match , -1, sizeof(match) );
    for( ll i = 1 ; i <= n ; i++ ){
        memset( vy , false , sizeof(vy) );
        bfs(i);
    }
    ll res = 0 ;
    for( ll i = 1 ; i <= n ; i++ ){
        if( match[i] != -1 ){
            res += w[match[i]][i] ;
        }
    }
    return res;
}
 
int main()
{
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(ll i=1;i<=n;i++) scanf("%lld",&p[i]);
    for(ll i=1;i<=n;i++) scanf("%lld",&b[i]);
    for(ll i=1;i<=n;i++) scanf("%lld",&c[i]);
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=n;j++){
            ll s=0;
            for(ll k=1;k<=n;k++){
                if(b[i]+c[j]>a[k]) s+=p[k];
            }
            w[i][j]=s;
        }
    }
    printf("%lld\n",KM());
    return 0;
}

虚树

P2495

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 250000 + 5;
const int M = 2*N;
int n, q, k, a[N], mark[N];
int head[N], ver[M], edge[M], nxt[M], tot;
int dfn[N], cid[N], cnt;
int dep[N], f[N][20], minval[N][20];
int st[N], top;
ll d[N];
struct Graph{
    int head[N], ver[M], edge[M], nxt[M], tot;
    void add(int x, int y, int z){
        ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    }
}G;
void add(int x, int y, int z){
    ver[++tot] = y, nxt[tot] = head[x], edge[tot] = z, head[x] = tot;
}
void dfs(int x,int fa){
    dfn[x] = ++cnt, cid[cnt] = x;
    for(int i=head[x];i;i=nxt[i]){
        int y = ver[i];
        if(y == fa)continue;
        f[y][0] = x;
        dep[y] = dep[x] + 1;
        minval[y][0] = edge[i];
        dfs(y, x);
    }
}
int MinVal;
int lca(int x, int y){
    MinVal = inf;
    if(dep[x] > dep[y]) swap(x, y);
    for(int i=19;i>=0;i--){
        if(dep[f[y][i]] >= dep[x]){
            MinVal = min(MinVal, minval[y][i]);
            y = f[y][i];
        }
    }
    if(x == y)return x;
    for(int i=19;i>=0;i--){
        if(f[y][i] != f[x][i]){
            MinVal = min(MinVal, min(minval[x][i], minval[y][i]));
            y = f[y][i], x = f[x][i];
        }
    }
    return f[x][0];
}
void insert(int x){
    if(x == 1) return;
    int t = lca(x, st[top]);
    if(t != st[top]){
        while(top > 1 && dfn[st[top-1]] > dfn[t]){
            lca(st[top-1], st[top]);
            G.add(st[top-1], st[top], MinVal);
            top --;
        }
        if(dfn[t] > dfn[st[top-1]]){ // t 不在栈中
            G.head[t] = 0; lca(t, st[top]);
            G.add(t, st[top], MinVal); st[top] = t;
        } else { // 说明 t 已经在栈中
            lca(t,st[top]); 
            G.add(t, st[top--], MinVal);
        }
    }
    G.head[x] = 0, st[++top] = x;
}
bool cmp(int x, int y){return dfn[x] < dfn[y];}
void build(){
    sort(a + 1, a + 1 + k, [=](const int a, const int b)->bool{return dfn[a] < dfn[b];});
    st[top=1] = 1; G.tot = 0, G.head[1] = 0;
    for(int i=1,l;i<=k;i++) insert(a[i]);
    for(int i=1;i<top;i++){
        lca(st[i], st[i+1]); G.add(st[i], st[i+1], MinVal);
    } 
}
void dfs(int x){
    d[x] = 0;
    for(int i=G.head[x];i;i=G.nxt[i]){
        int y = G.ver[i];
        //cout << x << ' ' << y << ' ' << G.edge[i] << endl;
        dfs(y);
        if(mark[y]) d[x] += G.edge[i];
        else d[x] += min(1ll*G.edge[i], d[y]);
    }
}

int main() {
    memset(minval, 0x3f, sizeof minval);
    scanf("%d",&n);
    for(int i=1,x,y,z;i<n;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    dep[1] = 1;
    dfs(1, 0);
    for(int j=1;j<20;j++)
        for(int i=1;i<=n;i++) 
            f[i][j] = f[f[i][j-1]][j-1], minval[i][j] = min(minval[i][j-1], minval[f[i][j-1]][j-1]);
    scanf("%d",&q);
    while(q--){
        scanf("%d",&k);
        for(int i=1;i<=k;i++)scanf("%d",&a[i]), mark[a[i]] = 1;
        build();
        dfs(1);
        printf("%lld\n", d[1]);
        for(int i=1;i<=k;i++) mark[a[i]] = 0;
    }
    return 0;
}

网络流

一定要注意边序号初始为1

最大流

1. \(EK\)

\(O(nm^2)\) 一般可以处理\(10^3\sim 10^4\) 规模的网络

const int inf = 1 << 29, N = 2010, M = 20010;
int head[N],ver[M],edge[M],nxt[M],v[N],incf[N],pre[N];
int n,m,s,t,tot,maxflow;
void add(int x,int y,int z){
    v[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
}
bool bfs(){
    memset(v,0,sizeof v);
    queue<int> q;
    q.push(s); v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front(); q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i]){
                int y = ver[i];
                if(v[y])continue;
                incf[y] = min(incf[y], edge[i]);
                pre[y] = i;
                q.push(y), v[y] = 1;
                if(y == t) return 1;
            }
        }
    }
    return 0;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
}
int main(){
    while(cin >> n >> m){
        memset(head,0,sizeof head);
        s = 1, t = n, tot = 1, maxflow = 0;
        for(int i=1;i<=m;i++){
            int x,y,z;scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);add(y,x,z);
        }
        while(bfs()) update();
        cout << maxflow << endl;
    }
}
2. Dinic

\(O(n^2m)\) 该算法求解二分图最大匹配的时间复杂度为\(O(m\sqrt{n})\)

const int inf = 1<<29, N = 50010,M=30010;
int head[N], ver[M], edge[M], nxt[M], d[N];
int n, m, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
    memset(d, 0, sizeof d);
    while(q.size())q.pop();
    q.push(s);d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                q.push(ver[i]);
                d[ver[i]] = d[x] + 1;
                if(ver[i] == t) return 1;
            }
        }
    }
    return 0;
}
int dinic(int x, int flow){
    if(x == t) return flow;
    int rest = flow, k;
    for(int i=head[x];i && rest; i=nxt[i]){
       	if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(rest, edge[i]));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i ^ 1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}
int main(){
    cin >> n >> m;
    cin >> s >> t;
    tot = 1;
    for(int i=1;i<=m;i++){
        int x, y, c;scanf("%d%d%d",&x,&y,&c);
        add(x,y,c);
    }
    int flow = 0;
    while(bfs())
        while(flow = dinic(s,inf)) maxflow += flow;
    cout << maxflow << endl;
}

费用流

最大费用最大流,用spfa找到最长路进行增广

const int inf = 0x3f3f3f3f;
const int N = 5000 + 5;
const int M = 200010;
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int n, k, tot, s, t, maxflow, ans;
void add(int x,int y,int z,int c){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
inline int num(int i, int j, int k) { return (i - 1) * n + j + k * n * n; }
bool spfa(){
    queue<int> q;
    memset(d, 0xcf, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = head[x]; i;i=nxt[i]){
            if(!edge[i])
                continue;
            int y = ver[i];
            if(d[y] < d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y])
                    v[y] = 1, q.push(y);
            }
        }
    }
    if(d[t] == 0xcfcfcfcf)
        return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += d[t] * incf[t];
}
int main()
{
    cin >> n >> k;
    s = 1, t = 2 * n * n;
    tot = 1;
    for (int i = 1; i <= n;i++){
        for (int j = 1; j <= n;j++){
            int c;
            scanf("%d", &c);
            add(num(i, j, 0), num(i, j, 1), 1, c);
            add(num(i, j, 0), num(i, j, 1), k - 1, 0);
            if(i < n)
                add(num(i, j, 1), num(i + 1, j, 0), k, 0);
            if(j < n)
                add(num(i, j, 1), num(i, j + 1, 0), k, 0);
        }
    }
    while(spfa()){
        update();
    }
    cout << ans << endl;
    return 0;
}

技巧

调试

#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }

快读

inline int read(){
    int x = 0,  f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x<<1) + (x<<3) + (ch^48);ch=getchar();}
    return x * f;
}

快写

void put(int x){
    int num = 0; char c[15];
    while(x) c[++num] = (x % 10) + 48, x /= 10;
    while(num) putchar(c[num--]);
    putchar('\n');
}

枚举子集

S是一个二进制数,表示一个集合,可以用S0=S(初始),S0=(S0-1)&S(下一个)这种方法枚举遍S的所有子集。
注意到这种枚举方法是二进制数值上从大到小枚举子集的。用归纳法证明合理性,假设枚举到某个S0,大于它的所有S子集都枚举过了,下一个枚举S1=(S0-1)&S,需要证明S1是S0紧挨着的下一个子集,才能保证枚举不漏,也就是要证明区间(S1,S0)里不存在S的其他子集。
设S0以k(k=0,1,2…)个0结尾,S0=xxxx10...0,则S0-1=xxxx01...1,S1=(S0-1)&S,易见S1是以xxxx0开头的最大子集,而S0是以xxxx1开头的最小子集,如果存在某子集\(S1<Sx<S0\),Sx要么以xxxx0开头,要么以xxxx1开头,无论哪种情况,都会出现矛盾。

复杂度\(O(3^n)\)

\(3^15=14348907,3^14=4782969\)

for (int S=1; S<(1<<n); ++S){
    for (int S0=S; S0; S0=(S0-1)&S)
        //do something.

如果不包含自身

for (int S=1; S<(1<<n); ++S){
    for (int S0=(S-1)&S; S0; S0=(S0-1)&S)
        //do something.
}

大数

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct BigInteger{
    //BASE为vector数组中一位中最大存储的数字,前面都是以10计算的
    //WIDTH为宽度
    static const int BASE = 100000000;
    static const int WIDTH = 8;
    vector<int> s;
    //正数为1,负数为-1
    int flag = 1;
    
    //构造函数
    BigInteger(ll num = 0){*this = num;}
    BigInteger(string str){*this = str;}
    BigInteger(const BigInteger& t){
        this->flag = t.flag;
        this->s = t.s;
    }
    //赋值函数
    BigInteger operator = (ll num){
        s.clear();
        do {
            s.push_back(num % BASE);
            num /= BASE;
        }while(num > 0);
        return *this;
    }
    BigInteger operator = (string &str){
        s.clear();
        int x,len = (str.length()-1)/WIDTH + 1;
        for(int i=0;i<len;i++){
            int end = str.length() - i*WIDTH;
            int start = max(0,end - WIDTH);
            sscanf(str.substr(start,end-start).c_str(),"%d",&x);
            s.push_back(x);
        }
        return *this;
    }

    //基本比较函数 A < B
    bool cmp( vector<int> &A, vector<int> & B){
        if(A.size() != B.size())return A.size() < B.size();
        for(int i=A.size()-1;i>=0;i--){
            if(A[i] != B[i]){
                return A[i] < B[i];
            }
        }
        return false;
    }
    //比较函数如果小于则返回真
    bool operator < ( BigInteger & b){
        return cmp(s,b.s);
    }
    bool operator > ( BigInteger& b){
        return b < *this;
    }
    bool operator <= ( BigInteger &b){
        return !(b < *this);
    }
    bool operator >= ( BigInteger &b){
        return !(*this < b);
    }
    bool operator == ( BigInteger &b){
        return !(b < *this) && (*this < b);
    }
    //基本四则运算
    vector<int> add(vector<int> &A, vector<int> &B);
    vector<int> sub(vector<int> &A, vector<int> &B);
    vector<int> mul(vector<int> &A, int b);
    vector<int> mul(vector<int> &A, vector<int> &B);
    vector<int> div(vector<int> &A, int b);
    vector<int> div(vector<int> A, vector<int> B);

    //重载运算符
    BigInteger operator + (BigInteger &b);
    BigInteger operator - (BigInteger &b);
    BigInteger operator * (BigInteger &b);
    BigInteger operator * (int& b);
    BigInteger operator / (BigInteger & b);
    BigInteger operator / (int b);
};
//重载<<
ostream& operator << (ostream &out,const BigInteger& x) {
    if(x.flag == -1)out << '-';
    out << x.s.back();
    for(int i = x.s.size() - 2; i >= 0;i--){
        char buf[20];
        sprintf(buf,"%08d",x.s[i]);//08d此处的8应该与WIDTH一致
        for(int j=0;j<strlen(buf);j++)out<<buf[j];
    }
    return out;
}
//重载输入
istream& operator >> (istream & in,BigInteger & x){
    string s;
    if(!(in>>s))return in;
    x = s;
    return in;
}
vector<int> BigInteger::add( vector<int> &A, vector<int> &B){
    if(A.size() < B.size())return add(B,A);
    int t = 0;
    vector<int> C;
    for(int i=0;i<A.size();i++){
        if(i<B.size())t += B[i];
        t += A[i];
        C.push_back(t%BASE);
        t /= BASE;
    }
    if(t)C.push_back(t);
    while(C.size() > 1 && C.back() == 0)C.pop_back();
    return C;
}
vector<int> BigInteger::sub( vector<int> &A, vector<int> &B){
    vector<int> C;
    for(int i=0,t=0;i<A.size();i++){
        t = A[i] - t;
        if(i<B.size())t -= B[i];
        C.push_back((t+BASE)%BASE);
        if(t < 0)t = 1;
        else t = 0;
    }
    while(C.size() > 1 && C.back() == 0)C.pop_back();
    return C;
}
vector<int> BigInteger::mul(vector<int> &A,int b){
    vector<int> C;
    int t = 0;
    for(int i = 0;i < A.size() || t; i++){
        if(i < A.size()) t += A[i] * b;
        C.push_back(t%BASE);
        t /= BASE;
    }
    return C;
}
//大数乘大数乘法需要将BASE设置为10,WIDTH设置为1
vector<int> BigInteger::mul( vector<int> &A, vector<int> &B) {
    int la = A.size(),lb = B.size();
    vector<int> C(la+lb+10,0);
    for(int i=0;i<la;i++){
        for(int j=0;j<lb;j++){
            C[i+j] += A[i] * B[j];
        }
    }
    for(int i=0;i<C.size();i++){
        if(C[i] >= BASE){
            C[i + 1] += C[i] / BASE;
            C[i] %= BASE;
        }
    }
    while(C.size() > 1 && C.back() == 0)C.pop_back();
    return C;
}
//大数除以整数
vector<int> BigInteger::div(vector<int> & A,int b){
    vector<int> C;
    int r = 0;
    for(int i = A.size() - 1;i >= 0;i--){
        r = r * 10 + A[i];
        C.push_back(r/b);
        r %= b;
    }
    reverse(C.begin(),C.end());
    while(C.size() > 1 && C.back() == 0)C.pop_back();
    return C;
}
//大数除以大数
vector<int> BigInteger::div(vector<int> A,vector<int> B){
    int la = A.size(),lb = B.size();
    int dv = la - lb; // 相差位数
    vector<int> C(dv+1,0);
    //将除数扩大,使得除数和被除数位数相等
    reverse(B.begin(),B.end());
    for(int i=0;i<dv;i++)B.push_back(0);
    reverse(B.begin(),B.end());
    lb = la;
    for(int j=0;j<=dv;j++){
        while(!cmp(A,B)){
            A = sub(A,B);
            C[dv-j]++;
        }
        B.erase(B.begin());
    }
    while(C.size()>1 && C.back() == 0)C.pop_back();
    return C;
}
BigInteger BigInteger::operator + ( BigInteger & b){
    BigInteger c;
    c.s.clear();
    c.s = add(s,b.s);
    return c;
}

BigInteger BigInteger::operator - ( BigInteger & b) {
    BigInteger c;
    c.s.clear();
    if(*this < b){
        c.flag = -1;
        c.s = sub(b.s,s);
    }
    else{
        c.flag = 1;
        c.s = sub(s,b.s);
    }
    return  c;
}
BigInteger BigInteger::operator *(BigInteger & b){
    BigInteger c;
    c.s = mul(s,b.s);
    return c;
}
BigInteger BigInteger::operator *(int& b){
    BigInteger c;
    c.s = mul(s,b);
    return c;
}
BigInteger BigInteger::operator /(BigInteger & b){
    BigInteger c;
    if(*this < b){
        c.s.push_back(0);
    }
    else{
        c.flag = 1;
        c.s = div(s,b.s);
    }
    return c;
}
BigInteger BigInteger::operator /(int b){
    BigInteger c;
    BigInteger t = b;
    if(*this < t){
        c.s.push_back(0);
    }
    else{
        c.flag = 1;
        c.s = div(s,b);
    }
    return c;
}
int main(){
    BigInteger A,B;
    cin>>A>>B;
    cout<<A+B<<endl;
    cout<<A-B<<endl;
    cout<<A*B<<endl;
    cout<<A/B<<endl;
    return 0;
}

提交前必看!

  1. 有没有爆int,有没有爆longlong?
  2. 有没有把数组错开成插入?
  3. 有没有把调试信息删除干净?

猜你喜欢

转载自www.cnblogs.com/1625--H/p/12607645.html