OI模板

待完成:拓扑排序(还有其他的想不起来了)

快读

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

快速乘法

ll fast_mul(ll a, ll b, ll mod) {
    ll res = 0;
    while (b > 0) {
        if (b & 1) {
            res = (res + a) % mod;
        }
        a = (a + a) % mod;
        b >>= 1;
    }
    return res;
}

快速幂

ll fast_pow(ll a, ll b, ll mod) {
    ll res = 1;
    while (b > 0) {
        if(b & 1) {
            res = res * a % mod;
            // res = fast_mul(res, a);
        }
        a = a * a % mod;
        // a = fast_mul(a, a);
        b >>= 1;
    }
    return res;
}

二分法求平方根

const double eps = 1e-7;
double get_sqrt(double k) {
    double l = 0, r = k;
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (mid * mid <= k) {
            l = mid;
        }
        else {
            r = mid;
        }
    }
    return l;
} 
int main() {
    double n;
    cin >> n;
    double ans = get_sqrt(n);
    printf("%.5lf\n", ans);
    return 0;
}

二分图判定

染色法:如果当前点未被染色,则从此点开始进行 DFS 并进行染色,对路径上的点交替染色。 如果发现在某个点时矛盾,原图就不为二分图。

int n, m;  // n 表示点数, m 表示边数
int ans[N]; // 标记每个点被染的颜色
bool dfs(int u, int color) {
    ans[u] = color;
    for (int i = p[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if (!ans[v]) {
            if (!dfs(v, 1 - color)) {
                return false;
            }
        } else if (ans[v] == color) {
            return false;
        }
    }
    return true;
}
bool judge_bipartite(int n) {
    for (int i = 0; i < n; i++) {
        if (!ans[i]) {
            if (!dfs(i, 0)) {
                return false;
            }
        }
    }
  return true;
}

并查集

int fa[N], n;
void init() {
    for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) {
    if (fa[x] == x) return fa[x];
    return fa[x] = find(fa[x]);
}
void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x != y) f[x] = y;
}

树的重心

int n;
int minNode = -1, minBalance = MAX_N;
int dfs(int u, int pre) {
    int sz = 0, maxSubtree = 0;
    for (int i = p[u]; i != -1; i = E[i].next) {
        if (E[i].v != pre) {
            int son = dfs(E[i].v, u);
            sz += son;
            maxSubtree = max(maxSubtree, son);
        }
    }
    sz++;
    maxSubtree = max(maxSubtree, n - sz);
    if (maxSubtree < minBalance) {
        minBalance = maxSubtree;
        minNode = u;
    }
    return sz;
}
int main() {
    cin >> n;
    init();
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs(1, 0);
    cout << minNode << endl;
    return 0;
}

树的直径

const int MAX_N = 100;
const int MAX_M = 10000;
struct Edge {
    int v, next;
    int len;
} E[MAX_M];
int p[MAX_N], eid;
void init() {
    memset(p, -1, sizeof(p));
    eid = 0;
}
void insert(int u, int v) {
    E[eid].v = v;
    E[eid].next = p[u];
    p[u] = eid++;
}
int maxlen,point;
void dfs(int u,int pre,int step) {
    if(step>maxlen) {
        maxlen=step;
        point=u;
    }
    for(int i=p[u];i!=-1;i=E[i].next) {
        if(E[i].v!=pre) {
            dfs(E[i].v,u,step+1);
        }
    }
}
int diameter() {
    maxlen=-1;
    dfs(1,0,0);
    maxlen=-1;
    dfs(point,0,0);
    return maxlen;
}
int main() {
    int n;
    cin >> n;
    init();
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        insert(u, v);
        insert(v, u);
    }
    cout << diameter() << endl;
    return 0;
}

二叉树遍历

const int N = 1e3 + 10;
int son[N][2], root;
vector<int> v1, v2, v3, v4;
void build() {
    root = 7;
    son[7][0] = 1;
    son[7][1] = 6;
    son[1][1] = 4;
    son[4][0] = 3;
    son[4][1] = 2;
    son[6][0] = 5;
}
void print() {
    cout << "preorder:";
    for (int i = 0; i < v1.size(); i++) {
        cout << v1[i] << " ";
    }
    cout << endl;
    cout << "inorder:";
    for (int i = 0; i < v2.size(); i++) {
        cout << v2[i] << " ";
    }
    cout << endl;
    cout << "postorder:";
    for (int i = 0; i < v3.size(); i++) {
        cout << v3[i] << " ";
    }
    cout << endl;
    cout << "levelorder:";
    for (int i = 0; i < v4.size(); i++) {
        cout << v4[i] << " ";
    }
    cout << endl;
}
// 先序遍历:根左右
void preorder(int u) {
    if (!u) return ;
    v1.push_back(u);
    preorder(son[u][0]);
    preorder(son[u][1]);
}
// 中序遍历:左根右
void inorder(int u) {
    if (!u) return ;
    inorder(son[u][0]);
    v2.push_back(u);
    inorder(son[u][1]);
}
// 后序遍历:左右根
void postorder(int u) {
    if (!u) return ;
    postorder(son[u][0]);
    postorder(son[u][1]);
    v3.push_back(u);
}
// 层序遍历
void levelorder() {
    queue<int> q;
    q.push(root);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        v4.push_back(u);
        if (son[u][0]) q.push(son[u][0]);
        if (son[u][1]) q.push(son[u][1]);
    }
}
int main() {
    build();
    preorder(root);
    inorder(root);
    postorder(root);
    levelorder();
    print();
    return 0;
}

确定二叉树

根据先序遍历和中序遍历确定二叉树

int n, a[N], b[N], son[N][2];
// a数组为中序遍历,b数组为先序遍历
// x1、y1为当前子树在a数组中下标的范围,x2为前子树在b数组中下标的起点
void dfs(int x1, int y1, int x2) {
    int pos = x1;
    while (a[pos] != b[x2]) { //在中序遍历里找到当前子树的根
        pos++;
    }
    int len1 = pos - x1, len2 = y1 - pos; //在中序遍历里确定当前子树的左子树和右子树大小
    if (len1) { //如果有左子树,那么根据先序遍历确定这个节点的左孩子
        son[b[x2]][0] = b[x2 + 1];
        dfs(x1, pos - 1, x2 + 1); //递归进入左子树
    }
    if (len2) { //如果有右子树,那么根据先序遍历确定这个节点的右孩子
        son[b[x2]][1] = b[x2 + 1 + len1];
        dfs(pos + 1, y1, x2 + 1 + len1); //递归进入右子树
    }
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i <= n; i++) {
        cin >> b[i];
    }
    dfs(1, n, 1);
    for (int i = 1; i <= n; i++) {
        cout << son[i][0] << " " << son[i][1] << endl;
    }
    return 0;
}

堆的实现

/** 插入操作: 向堆中插入一个新元素;在数组的最末尾插入新结点。
 * 然后自下而上调整子结点与父结点:
 * 比较当前结点与父结点,不满足堆性质则交换,使得当前子树满足二叉堆的性质
 * 时间复杂度:O(log n)
**/
void push(int a[], int i, int& n) { // i为插入的值,n为插入之前堆得大小
    n++; // 调整大小
    a[n] = i; // 放进堆得最后
    int p = n;
    while (p > 1 && a[p / 2] > a[p]) { // 调整,如果不满足堆得性质,交换父节点和当前节点
        swap(a[p / 2], a[p]);
        p /= 2;
    }
}

/**
 * 弹出操作:删除堆顶元素,再把堆存储的最后那个结点填在根结点处。再从上而下调整父结点与它的子节点。
 * 时间复杂度:O(log n)
**/ 
int pop(int a[], int &n) {
    int res = a[1]; // 记录堆顶元素
    a[1] = a[n]; // 把堆顶元素换到最后
    n--; // 调整大小,此时最后一位虽然有值但不再次使用
    int p = 1, t;
    while (p * 2 <= n) { //调整
        if (p * 2 + 1 > n || a[p * 2 + 1] >= a[p * 2]) { // 找到左右两个儿子较小者或没有友儿子 
            t = p * 2;
        }
        else {
            t = p * 2 + 1;
        }
        if (a[p] > a[t]) { // 如果不满足堆得性质
            swap(a[p], a[t]);
            p = t;
        }
        else break; // 否则调整完成
    }
    return res;
}

/**
 * 删除操作:使该元素与堆尾元素交换,调整堆容量,再由原堆尾元素的当前位置自顶向下调整。
 * 时间复杂度:O(log n)
 * 与弹出操作类似
**/

归并排序

int n, a[N], t[N];
void merge_sort(int x, int y) {
    if (y - x > 1) {
        int m = (x + y) / 2;
        merge_sort(x, m);
        merge_sort(m, y);
        int p = x, q = m, i = x;
        while (p < m || q < y) {
            if (q >= y || p < m && a[p] <= a[q]) t[i++] = a[p++];
            else t[i++] = a[q++];
        }
        for (int i = x; i < y; i++) a[i] = t[i];
    }
}
int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    merge_sort(0, n); // 左闭右开
    for (int i = 0; i < n; i++) cout << a[i] << " ";
    cout << "\n";
    return 0;
}

矩阵乘法

struct matrix {
    int a[100][100];
    int n, m;
};
matrix matrix_mul(matrix A, matrix B) {
    matrix ret;
    ret.n = A.n;
    ret.m = B.m;
    memset(ret.a, 0, sizeof(ret.a));
    for (int i = 0; i < ret.n; i++) {
        for (int j = 0; j < ret.m; j++) {
            for (int k = 0; k < A.m; k++) {
                ret.a[i][j] += A.a[i][k] * B.a[k][j];
            }
        }
    }
    return ret;
}
int main() {
  	matrix A, B;
    cin >> A.n >> A.m;
    for (int i = 0; i < A.n; i++) {
        for (int j = 0; j < A.m; j++) {
            cin >> A.a[i][j];
        }
    }
    cin >> B.n >> B.m;
    for (int i = 0; i < B.n; i++) {
        for (int j = 0; j < B.m; j++) {
            cin >> B.a[i][j];
        }
    }
    if (A.m != B.n) {
        cout << "No" << endl;
    }
    else {
        matrix C = matrix_mul(A, B);
        for (int i = 0; i < C.n; i++) {
            for (int j = 0; j < C.m; j++) {
                cout << C.a[i][j] << " ";
            }
            cout << endl;
        }
    }
    return 0;
}
/*
简单写法
#include <bits/stdc++.h>
using namespace std;
int n, m, k, a[107][107], b[107][107], c[107][107];

int main() {
    scanf("%d%d%d", &n, &m, &k)
    for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
    for(int i = 1; i <= m; ++i) for(int j = 1; j <= k; ++j) scanf("%d", &b[i][j]);
    for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) for(int l = 1; l <= k; ++l) c[i][j] += a[i][l] * b[l][j];
    for(int i = 1; i <= n; ++i) {for(int j = 1; j <= k; ++j) printf("%d", c[i][j]); puts("");}
    return 0;
}
*/

矩阵快速幂

const int Mod = 1e9 + 7;
struct matrix {
   int a[N][N];
   int n;
};
matrix matrix_mul(matrix A, matrix B) {
    matrix ret;
    ret.n = A.n;
    memset(ret.a, 0, sizeof(ret.a));
    for (int i = 0; i < ret.n; i++) {
        for (int j = 0; j < ret.n; j++) {
            for (int k = 0; k < A.n; k++) {
                ret.a[i][j] = (ret.a[i][j] + A.a[i][k] * B.a[k][j] % Mod) % Mod;
            }
        }
    }
    return ret;
}
matrix unit(int n) {
    matrix ret;
    ret.n = n;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (i == j) {
                ret.a[i][j] = 1;
            } else {
                ret.a[i][j] = 0;
            }
        }
    }
    return ret;
}
matrix matrix_pow(matrix A, int p) {
    matrix res = unit(A.n);
    while (p > 0) {
        if (p & 1) {
            res = matrix_mul(res, A);
        }
        A = matrix_mul(A, A);
        p >>= 1;
    }
    return res;
}
int main() {
    matrix A;
    int p;
    cin >> A.n >> p;
    for (int i = 0; i < A.n; i++) {
        for (int j = 0; j < A.n; j++) {
            cin >> A.a[i][j];
        }
    }
    matrix C = matrix_pow(A, p);
    for (int i = 0; i < C.n; i++) {
        for (int j = 0; j < C.n; j++) {
            cout << C.a[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

矩阵快速幂求斐波那契数列

typedef long long ll;
const ll N = 110;
ll Mod;
struct matrix {
   ll a[N][N];
   ll n, m;
};
matrix matrix_mul(matrix A, matrix B) {
    matrix ret;
    ret.n = A.n; ret.m = B.m;
    memset(ret.a, 0, sizeof(ret.a));
    for (ll i = 0; i < ret.n; i++) {
        for (ll j = 0; j < ret.m; j++) {
            for (ll k = 0; k < A.m; k++) {
                ret.a[i][j] = (ret.a[i][j] + A.a[i][k] * B.a[k][j] % Mod) % Mod;
            }
        }
    }
    return ret;
}
matrix unit(ll n, ll m) {
    matrix ret;
    ret.n = n; ret.m = m;
    for (ll i = 0; i < n; i++) {
        for (ll j = 0; j < m; j++) {
            if (i == j) {
                ret.a[i][j] = 1;
            } else {
                ret.a[i][j] = 0;
            }
        }
    }
    return ret;
}
matrix matrix_pow(matrix A, ll p) {
    matrix res = unit(A.n, A.m);
    while (p > 0) {
        if (p & 1) {
            res = matrix_mul(res, A);
        }
        A = matrix_mul(A, A);
        p >>= 1;
    }
    return res;
}
int main() {
    matrix A;
    ll n;
    cin >> n >> Mod;
    A.n = A.m = 2;
    for (ll i = 0; i < 2; i++) {
        for (ll j = 0; j < 2; j++) {
            A.a[i][j] = 1;
        }
    }
    A.a[0][0] = 0;
    matrix B;
    B.n = 1; B.m = 2;
    B.a[0][0] = 1; B.a[0][1] = 1;
    matrix C = matrix_mul(B, matrix_pow(A, n - 1));
    cout << C.a[0][0] << endl;
    return 0;
}

\(\text{Bellman-Ford}\)

时间复杂度:\(O(VE)\)

int E,V;
int d[N];
struct edge{
	int from;
	int to;
	int cost;
}es[N];
void bellman_ford(int s) {
	for(int i=0;i<V;i++) d[i]=INF;
	d[s]=0;
	while(true) {
		bool update=false;
		for(int i=0;i<E;i++) {
			edge e=es[i];
			if(d[e.from]!=INF && d[e.to]>d[e.from]+e.cost) {
				d[e.to]=d[e.from]+e.cost;
				update=true;
                // 判断负环需要cnt数组,加到n时代表有负环
			}
		}
		if(!update) break;
	}
}
int main() {
	int s;
	cin>>E>>V>>s;
    s--;
	for(int i=0;i<E;i++) {
        int from,to;
        cin>>from>>to;
        from--;to--;
        es[i].from=from;
        es[i].to=to;
        cin>>es[i].cost;
    }
	bellman_ford(s);
    for(int i=0;i<V;i++) cout<<d[i]<<"\n";
	return 0;
}

\(\text{SPFA}\)

最好时间复杂度:\(O(kE)\) ,其中 \(k\) 为常数。

最坏时间复杂度:\(O(VE)\)

struct node
{
    int v, w;
    node(int vv, int ww)
    {
        v = vv;
        w = ww;
    }
};
vector<node> g[N];
int n, m, s;
int d[N];
bool in_queue[N];
queue<int> q;
int main()
{
    cin >> n >> m >> s;
    for (int i = 0; i < m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        g[u].push_back(node(v, w));
        g[v].push_back(node(u, w));
    }
    memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    in_queue[s] = true;
    q.push(s);
    while (!q.empty())
    {
        int v = q.front();
        q.pop();
        in_queue[v] = false;
        for (int i = 0; i < g[v].size(); i++)
        {
            int x = g[v][i].v;
            if (d[x] > d[v] + g[v][i].w)
            {
                d[x] = d[v] + g[v][i].w;
                if (!in_queue[x])
                {
                    q.push(x);
                    in_queue[x] = true;
                }
                // 判断负环同bellman-ford
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        cout << d[i] << " ";
    }
    return 0;
}

\(\text{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++) {
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
            }
        }
    }
}

\(\text{Dijkstra}\)

无堆优化时间复杂度: \(O(V^2)\)

堆优化时间复杂度: \(O((V + E) \log V)\)

typedef pair<int,int> P;
struct edge{
	int to,cost;
};
int n,m;
vector<edge> g[N];
int d[N];
void dijkstra(int s) {
	priority_queue<P,vector<P>,greater<P> > que;
	for(int i=0;i<n;i++) d[i]=INT_MAX;
	d[s]=0;
	que.push(P(0,s));
	while(!que.empty()) {
		int mind=que.top().first;
		int v=que.top().second;
		que.pop();
		if(d[v]!=mind) continue;
		for(int i=0;i<g[v].size();i++) {
			edge e=g[v][i];
			if(d[e.to]>d[v]+e.cost) {
				d[e.to]=d[v]+e.cost;
				que.push(P(d[e.to],e.to));
			}
		}
	}
}
int main() {
	int s;
	cin>>n>>m>>s;
	s--;
	for(int i=0;i<m;i++) {
		int from;
		edge e;
		cin>>from>>e.to>>e.cost;
		from--;e.to--;
		g[from].push_back(e);
	}
	dijkstra(s);
	for(int i=0;i<n;i++) cout<<d[i]<<" ";
	cout<<"\n";
	return 0;
}

\(\text{Kruskal}\)

int n, m;
struct Edge {
    int u, v;
    int len;
} E[MAX_M];
bool cmp (Edge a, Edge b) {
    return a.len < b.len;
}
int fa[MAX_N];
void init() {
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
    }
}
int find(int x) {
    if (fa[x] == x) {
        return x;
    }
    return fa[x] = find(fa[x]);
}
int kruskal(int n,int m) {
    int sum=0;
    init();
    sort(E,E+m,cmp);
    for(int i=0;i<m;i++) {
        int fu=find(E[i].u);
        int fv=find(E[i].v);
        if(fu!=fv) {
            fa[fv]=fu;
            sum+=E[i].len;
        }
    }
    return sum;
}
int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        cin >> E[i].u >> E[i].v >> E[i].len;
    }
    cout << kruskal(n, m) << endl;
    return 0;
}

\(\text{ST}\)算法

RMQ问题,求区间最值。

时间复杂度:

  • 预处理: \(O(n\log n)\)
  • 单次询问: \(O(1)\)
const int M = 25;
int n, f[N][M], a[N];
void init() {
    for (int i = 1; i <= n; i++) 
        f[i][0] = a[i];
    for (int j = 1; (1 << j) <= n; j++) // 也可以到20
        for (int i = 1; i + (1 << j) - 1 <= n; i++) // 和LCA加以区分
            f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); // 最大值
}
int query(int l, int r) {
    int i = log2(r - l + 1);
    return max(f[l][i], f[r - (1 << i) + 1][i]); 
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) 
        cin >> a[i];
    init(); 
    int Q;
    cin >> Q;
    while (Q--) {
        int l, r;
        cin >> l >> r;
        cout << query(l, r) << endl;    
    }
    return 0;
}

\(\text{LCA}\)

最近公共祖先问题,倍增算法

时间复杂度:

  • 预处理:\(O(n\log n)\)

  • 单词查询:\(O(\log n)\)

注意代码12行

const int M = 20;
int f[N][M], n, d[N];
vector<int> G[N];
void init() {
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i <= n; i++) { // 注意!i到n
            f[i][j] = f[f[i][j - 1]][j - 1];
        }
    }
}
void dfs(int u) { // 求深度,求每个节点的父亲
    d[u] = d[f[u][0]] + 1;
    for (int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if (v != f[u][0]) {
            f[v][0] = u;
            dfs(v);
        }
    }
}
int lca(int x, int y) {
    if (d[x] < d[y]) swap(x, y); // 让 x 成为较深的一点
    int K = 0;
    while ((1 << (K + 1)) <= d[x]) K++; // 找到不超过 x 的深度的最大的 2 ^ k    
    for (int j = K; j >= 0; j--) { // 让 x 往上跳,跳到与 y 同一高度处
        if (d[f[x][j]] >= d[y]) {
            x = f[x][j];
        }
    }
    if (x == y) return x; // 如果两个点相等,那么 y 是 x 的祖先
    for (int j = K; j >= 0; j--) { // 同时往上跳,跳到尽量高,但要求跳到的点不同
        if (f[x][j] != f[y][j]) {
            x = f[x][j];
            y = f[y][j];
        }
    }
    return f[x][0]; //  此时 x 和 y 的父亲节点就是 LCA 了
}
int main() {
    int Q;
    cin >> n >> Q;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1);
    init();
    while (Q--) {
        int x, y;
        cin >> x >> y;
        cout << lca(x, y) << endl;
    }
    return 0;
}

割点

\(\text{Tarjan}\) 算法

割点:在一个 无向连通图 中,如果删除某个点和这个点关联的所有边,剩下图的连通分量大于 \(1\) ,也即剩下的图不再连通,那么我们称这个点是 割点。

int n, m, times, dfn[N], low[N];
bool iscut[N]; // 标记是否是割点
struct edge {
    int u, v;
    int next;
} E[M];
int p[N], eid;
void add(int u, int v) {
    E[eid].u = u;
    E[eid].v = v;
    E[eid].next = p[u];
    p[u] = eid++;
}
void dfs(int u, int fa) {
    dfn[u] = low[u] = ++times;
    int child = 0; // 用来处理根结点子结点数
    for (int i = p[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if (!dfn[v]) { // v 没有被访问过,u, v 是树边
            child++;
            dfs(v, u);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) iscut[u] = true;
        }
        else if (v != fa) { // 反向边,注意 v == fa 的时候,是访问重复的边
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (fa < 0 && child == 1) {
        // fa < 0 表示根结点,之前根结点一定会被标记为割点, 取消之
        iscut[u] = false;
    }
}
int main() {
    memset(p, -1, sizeof(p));
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs(1, -1);
    for (int i = 1; i <= n; i++) {
        if (iscut[i]) cout << i << " ";
    }
    cout << endl;
    return 0;
}

点双连通分量

\(\text{Tarjan}\) 算法

点双连通图:一个 无向连通图 ,对于任意一个点,如果删除这个点和这个点关联的所有边,剩下的图还是连通的,那么称这个图是一个 点双连通图,也就是点双连通图中不会有割点出现。

点双连通分量:无向图 \(G\) 的的所有子图 \(G'\)中,如果 \(G'\) 是一个点双连通图,则称图 \(G'\) 为图 \(G\) 的点双连通子图。如果一个点双连通子图 \(G'\) 不是任何一个点双连通子图的真子集(包含但不相等),则图 \(G'\) 为图 G 的 极大点双连通子图,也称为点双连通分量。

int n, m, dfn[N], low[N], cnt, times; // cnt 为点双连通分量数量
bool cut[N]; // 标记是否是割点
set<int> bcc[N]; // 记录每个点双连通分量里面的点,用 set 可以去重
struct edge {
    int u, v;
    int next;
} E[N];
stack<edge> S; 
int p[N], eid;
void add(int u, int v) {
    E[eid].u = u;
    E[eid].v = v;
    E[eid].next = p[u];
    p[u] = eid++;
}
void dfs(int u, int fa) {
    dfn[u] = low[u] = ++times;
    int child = 0; // 用来处理根结点子结点数
    for (int i = p[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if (!dfn[v]) { // v 没有被访问过,u, v 是树边
            child++;
            S.push(E[i]);
            dfs(v, u); 
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) {
                cut[v] = true;
                cnt++; // 增加一个点双连通分量
                edge x;
                do {
                    x = S.top();
                    S.pop();
                    bcc[cnt].insert(x.u);
                    bcc[cnt].insert(x.v);
                } while (x.u != u || x.v != v);
            }
        }
        else if (v != fa) { // 反向边,注意 v == fa 的时候,是访问重复的边
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (fa < 0 && child == 1) {
        // fa < 0 表示根结点,之前根结点一定被标记为割点, 取消之
        cut[u] = false;
    }
}
int main() {
    memset(p, -1, sizeof(p));
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs(1, -1);
    cout << cnt << endl;
    for (int i = 1; i <= cnt; i++) {
        for (auto x : bcc[i]) cout << x << " ";
        cout << endl;  
    }
    return 0;
}

边双连通分量

\(\text{Tarjan}\) 算法

桥:在一个 无向连通图 中,如果删除某条边,剩下图的连通分量的个数大于 \(1\) ,也即剩下的图不再连通,那么我们称这条边是 桥。

求桥的程序与割点程序类似,略。

边双连通图:一个 无向连通图 ,对于任意一条边,如果删除这条边,剩下的图还是连通的,那么称这个图是一个 边双连通图,也就是边双连通图中不会有桥出现。

边双连通分量:无向图图 \(G\) 的的所有子图 \(G'\) 中,如果 \(G'\) 是一个边双连通图,则称图 \(G'\) 为图 \(G\) 的 边双连通子图。

如果一个边双连通子图 \(G'\) 不是任何一个边双连通子图的真子集,则 \(G'\) 为图 \(G\) 的 极大边双连通子图,也称为 边双连通分量。*

注意:一个点只可能存在于一个边双连通分量。

int n, m, times, dfn[N], low[N];
struct edge {
    int u, v;
    int next;
} E[N];
int p[N], eid, bcc_cnt; // bcc_cnt 为边双连通分量数量
stack<int> S;
vector<int> bcc[N]; // 记录每个点双连通分量里面的点
void add(int u, int v) {
    E[eid].u = u;
    E[eid].v = v;
    E[eid].next = p[u];
    p[u] = eid++;
}
void dfs(int u, int fa) {
    dfn[u] = low[u] = ++times;
    S.push(u);
    for (int i = p[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if (!dfn[v]) { // v 没有被访问过,u, v 是树边
            dfs(v, u);
            low[u] = min(low[u], low[v]);
        }
        else if (v != fa) { // 反向边,注意 v == fa 的时候,是访问重复的边
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (low[u] == dfn[u]) { // 此时 u 是根结点或者 fa -> u 是桥
        bcc_cnt++; // 增加一个边双连通分量
        int x;
        do { //从栈中弹出 u 及 u 之后的顶点
            x = S.top();
            S.pop();
            bcc[bcc_cnt].push_back(x);
        } while (x != u);
    }
}
int main() {
    memset(p, -1, sizeof(p));
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs(1, -1);
    cout << bcc_cnt << endl;
    for (int i = 1; i <= bcc_cnt; i++) {
        for (auto x : bcc[i]) cout << x << " ";
        cout << endl;
    } 
    return 0;
}

强连通分量

\(\text{Tarjan}\) 算法

强连通分量:如果 有向图 \(G\) 中任意两个点都 相互可达,则称图 \(G\) 是一个 强连通图。

代码后部有缩点。

int n, m, times, dfn[N], low[N];
struct edge {
    int v;
    int next;
} E[N];
int p[N], eid;
int scc_cnt; // 强连通分量数量
int sccno[N]; // 记录每个点属于的强连通分量的编号
stack<int> S;
vector<int> scc[N];
vector<int> G[N];
void dfs(int u) {
    dfn[u] = low[u] = ++times;
    S.push(u);
    for (int i = p[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if (!dfn[v]) { // v 没有被访问过,u, v 是树边
            dfs(v);
            low[u] = min(low[u], low[v]);
        }
        else if (!sccno[v]) { // 对于已经求出 scc 的点,直接删除
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (low[u] == dfn[u]) { // u 是第一个被探测到的点
        int x;
        scc_cnt++;
        do {
            x = S.top();
            S.pop();
            sccno[x] = scc_cnt;
            scc[scc_cnt].push_back(x);
        } while (x != u);
    }
}
void add(int u, int v) {
    E[eid].v = v;
    E[eid].next = p[u];
    p[u] = eid++;
}
int main() {
    memset(p, -1, sizeof(p));
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v);
    }
    for (int i = 1; i <= n; i++) { // 每个点都要尝试 dfs 一次
        if (!dfn[i]) {
            dfs(i);
        }
    }
    cout << scc_cnt << endl;
    for (int i = 1; i <= scc_cnt; i++) {
        for (auto x : scc[i]) cout << x << " ";
        cout << endl;
    }
    // 缩点
    for (int u = 1; u <= n; u++) {
        for (int i = p[u]; i != -1; i = E[i].next) {
            int v = E[i].v;
            if (sccno[u] != sccno[v]) G[sccno[u]].push_back(sccno[v]); // 构建新图
        }
    }
    return 0;
}

查分约束系统

差分约束系统:是最短路的一类经典应用。 如果一个不等式组由 \(n\) 个变量和 \(m\) 个约束条件组成,且每个约束条件都是形如 \(x_j - x_i \le k,1 \le i,j \le n\) 的不等式,则称其为 差分约束系统 (system of difference constraints)。

差分约束系统是求解一组变量的不等式组的算法。

两种连边方式:

  • 连边后求最短路的方法,对于 \(x_j - x_i \le k\) ,变形为 \(x_j \le x_i + k\)\(i\)\(j\) 连一条权值为 \(k\) 的边,加入超级源点,最后求出最短路。实际上表示在 \(x_i \le 0\) 的情况下,所有 \(x\) 的 最大 的解。若存在负环,就无解。

  • 连边后求最长路的方法,对于 \(x_j - x_i \le k\) ,变形为 \(x_i \ge x_j - k\)\(i\)\(j\) 连一条权值为 \(-k\) 的边,加入超级源点,最后求出最长路。实际上表示在 \(x_i \ge 0\) 的情况下,所有 \(x\) 的 最小 的解。若存在正环,就无解。

最短路

struct edge {
    int v, w, next;
} E[M];
int n, m, d[N], cnt[N], p[N], eid;
void add(int u, int v, int w) {
    E[eid].v = v;
    E[eid].w = w;
    E[eid].next = p[u];
    p[u] = eid++;
}
bool inqueue[N];
bool spfa() {
    memset(d, 0x3f, sizeof(d));
    queue<int> q;
    q.push(0);
    d[0] = 0;
    inqueue[0] = true;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inqueue[u] = false;
        for (int i = p[u]; i != -1; i = E[i].next) {
            int v = E[i].v, w = E[i].w;
            if (d[v] > d[u] + w) {
                d[v] = d[u] + w;
                if (!inqueue[v]) {
                    cnt[v] = cnt[u] + 1;
                    inqueue[v] = true;
                    q.push(v);
                    if (cnt[v] > n + 1) return false;
                }
            }
        }
    }
    return true;
}
int main() {
    cin >> n >> m;
    memset(p, -1, sizeof(p));
    for (int i = 1; i <= m; i++) {
        int op, u, v, w;
        cin >> op;
        cin >> u >> v >> w;
        if (op == 1) { // 表示 x_u - x_v <= w,则 x_u <= x_v + w
            add(v, u, w);
        }
        else if (op == 2) { // 表示 x_u - x_v >= w,则 x_v <= x_u - w
            add(u, v, -w);
        }
        else { // 表示 x_u - x_v = w,则 x_u <= x_v + w , x_v <= x_u - w
            add(u, v, -w);
            add(v, u, w);
        }
    }
    for (int i = 1; i <= n; i++) {
        add(0, i, 0);
    }
    if (!spfa()) {
        cout << "NO" << endl;
    }
    else {
        for (int i = 1; i <= n; i++) cout << d[i] << " ";
        cout << endl;
    }
    return 0;
}

最长路

struct edge {
    int v, w, next;
} E[M];
int n, m, d[N], cnt[N], p[N], eid;
void add(int u, int v, int w) {
    E[eid].v = v;
    E[eid].w = w;
    E[eid].next = p[u];
    p[u] = eid++;
}
bool inqueue[N];
bool spfa() {
    memset(d, -1, sizeof(d));
    queue<int> q;
    q.push(0);
    d[0] = 0;
    inqueue[0] = true;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inqueue[u] = false;
        for (int i = p[u]; i != -1; i = E[i].next) {
            int v = E[i].v, w = E[i].w;
            if (d[v] < d[u] + w) {
                d[v] = d[u] + w;
                if (!inqueue[v]) {
                    cnt[v] = cnt[u] + 1;
                    inqueue[v] = true;
                    q.push(v);
                    if (cnt[v] > n + 1) return false;
                }
            }
        }
    }
    return true;
}
int main() {
    cin >> n >> m;
    memset(p, -1, sizeof(p));
    for (int i = 1; i <= m; i++) {
        int op, u, v, w;
        cin >> op;
        cin >> u >> v >> w;
        if (op == 1) { // 表示 x_u - x_v <= w,则 x_v >= x_u - w
            add(u, v, -w);
        }
        else if (op == 2) { // 表示 x_u - x_v >= w,则 x_u >= x_v + w
            add(v, u, w);
        }
        else { // 表示 x_u - x_v = w,则 x_v >= x_u - w , x_u >= x_v + w
            add(u, v, -w);
            add(v, u, w);
        }
    }
    for (int i = 1; i <= n; i++) {
        add(0, i, 0);
    }
    if (!spfa()) {
        cout << "NO" << endl;
    }
    else {
        for (int i = 1; i <= n; i++) cout << d[i] << " ";
        cout << endl;
    }
    return 0;
}

\(2-\text{SAT}\) 问题

\(2-\text{SAT}\) :给出一些 \(and,or\) 等关系(如 \(a_x\) \(and\) \(a_y\) \(= 1\)) ,求出一组解。

这里给出比较暴力的一种方法

int n, m;
struct edge {
    int v, next;
} E[N];
int p[N * 2], eid, c;
bool mark[N * 2];
int S[N * 2];
void add(int u, int v) {
    E[eid].v = v;
    E[eid].next = p[u];
    p[u] = eid++;
}
bool dfs(int u) {
    if (mark[u ^ 1]) return false;
    if (mark[u]) return true;
    mark[u] = true;
    S[c++] = u;
    for (int i = p[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if (!dfs(v)) return false;
    }
    return true;
}
bool check() {
    for (int i = 1; i <= n * 2 ; i += 2) {
        if (!mark[i] && !mark[i + 1]) {
            c = 0;
            if (!dfs(i)) {
                while (c > 0) {
                    mark[S[--c]] = false;
                }
                if (!dfs(i + 1)) return false;
            }
        }
    }
    return true;
}
int main() {
    memset(p, -1, sizeof(p));
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int k, x, y;
        cin >> k >> x >> y;
        if (k == 0) { // x and y = 1
            add(2 * x, 2 * x + 1);
            add(2 * y, 2 * y + 1);
        }
        else { // x or y = 1
            add(2 * x, 2 * y + 1);
            add(2 * y, 2 * x + 1);
        }
    }
    if (check()) {
        cout << "YES" << endl;
    }
    else {
        cout << "NO" << endl;
    }
    return 0;
}

线段树

区间修改,区间求和(最值相似)

注意:\(d\) 数组和 \(lazy\) 标记数组开 \(4\) 倍大小

ll n, m, a[N], d[4 * N], lazy[4 * N];
void push_up(int p) {
    d[p] = d[p << 1] + d[p << 1 | 1];
}
void push_down(int p, int l, int r) {
    if (lazy[p]) {
        lazy[p << 1] += lazy[p];
        lazy[p << 1 | 1] += lazy[p];
        int mid = (l + r) >> 1;
        d[p << 1] += lazy[p] * (mid - l + 1);
        d[p << 1 | 1] += lazy[p] * (r - mid);
        lazy[p] = 0;
    }
}
void build(int p, int l, int r) {
    if (l == r) {
        d[p] = a[l];
        return ;
    }
    int mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    push_up(p);
}
void update(int p, int l, int r, int x, int y, ll v) {
    if (x <= l && r <= y) {
        lazy[p] += v;
        d[p] += v * (r - l + 1);
        return ;
    }
    push_down(p, l, r);
    int mid = (l + r) >> 1;
    if (x <= mid) update(p << 1, l, mid, x, y, v);
    if (y > mid) update(p << 1 | 1, mid + 1, r, x, y, v);
    push_up(p);
}
ll query(int p, int l, int r, int x, int y) {
    if (x <= l && r <= y) {
        return d[p];
    }
    push_down(p, l, r);
    int mid = (l + r) >> 1;
    ll res = 0;
    if (x <= mid) res += query(p << 1, l, mid, x, y);
    if (y > mid) res += query(p << 1 | 1, mid + 1, r, x, y); 
    return res;
}
int main() {
    n = rd(); m = rd();
    for (int i = 1; i <= n; i++) a[i] = rd();
    build(1, 1, n);
    while (m--) {
        ll op = rd(), x = rd(), y = rd(), k;
        if (op == 1) {
            k = rd();
            update(1, 1, n, x, y, k);
        }
        else {
            printf("%lld\n", query(1, 1, n, x, y));
        }
    }
    return 0;
}

树状数组

单点修改,区间求和(区间修改不会)

int C[N], n;
int lowbit(int x) {
    return x & (-x);
}
int getsum(int x) {
    int res = 0;
    for (int i = x; i > 0; i -= lowbit(i)) {
        res += C[i];
    }
    return res;
}
void update(int x, int v) {
    for (int i = x; i <= n; i += lowbit(i)) {
        C[i] += v;
    }
}
int query(int l, int r) {
    return getsum(r) - getsum(l - 1);
}
int main() {   
    int q;
    cin >> n >> q;
    for (int i = 1; i <= n; i++) {
        int v;
        cin >> v;
        update(i, v);
    }
    while (q--) {
        int op, l, r;
        cin >> op >> l >> r;
        if (op == 1) update(l, r);
        else cout << query(l, r) << endl;
    }
    return 0;    
}

区间修改求值

差分序列算法

int n, a[N], d[N];
int main() {
    int Q;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        d[i] = a[i] - a[i - 1];
    }
    cin >> Q;
    while (Q--) {
        int l, r, v;
        cin >> l >> r >> v;
        d[l] += v;
        d[r + 1] -= v;
    }
    for (int i = 1; i <= n; i++) {
        a[i] = a[i - 1] + d[i];
        cout << a[i] << " ";
    }
    return 0;
}

欧拉回路/路径

若图 \(G\) 中存在这样一条路径,使得它恰好通过 GG 中每条边一次,则称该路径为 欧拉路径。若该路径是一个环路,则称为 欧拉(Euler)回路

形象一点说,欧拉路问题就是一笔画问题,你可以从某个点开始一笔画出这个图,那这个图就具有欧拉路径,具体路径就是你画的这条,如果画完正好回到起点,那这就是一条欧拉回路。

具有欧拉回路的图称称为 欧拉图

具有欧拉路径但不具有欧拉回路的图称为 半欧拉图

无向图判欧拉回路/路径

判断方法:

  • 一个无向图 \(G\) 存在欧拉路径当且仅当无向图 \(G\) 是连通图,且该图中有两个奇度顶点(度数为奇数)或者无奇度顶点。
  • 当无向图 \(G\) 是包含两个奇度顶点的连通图时,\(G\) 的欧拉路径必定以这两个奇度顶点为端点。
  • 一个无向图 \(G\) 存在欧拉回路当且仅当无向图 \(G\) 连通且不存在奇度顶点。
vector<int> G[105];
int fa[105], degree[105];
int n, m, f, cnt;
void init() {
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
    }
}
int get(int x) {
    if (fa[x] == x) {
        return x;
    }
    return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
    x = get(x);
    y = get(y);
    if (x != y) {
        fa[y] = x;
        f--;
    }
}

int main() {
    cin >> n >> m;
    init();
    f = n;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
        merge(u, v);
        degree[u]++;
        degree[v]++;
    }
    if (f != 1) {
        cout << "No" << endl;
        return 0;
    }
    for(int i=1;i<=n;i++) {
        if(degree[i]%2==1) {
            cnt++;
        }
    }
    if(cnt==0) {
        cout<<"Euler loop"<<endl; //欧拉回路:欧拉路径是一个环
    }
    else if(cnt==2) {
        cout<<"Euler path"<<endl;
    }
    else {
        cout<<"No"<<endl;
    }
    return 0;
}

无向图求欧拉路径

struct node {
    int v;
    int id;
    node (int vv, int iid) {
        v = vv;
        id = iid;
    }
};
vector<node> G[10005];
int out[10005];
int n, m;
bool vis[100005];
void dfs(int u) {
    if (out[u]) {
        for (int i = 0; i < G[u].size(); i++) {
            if (!vis[G[u][i].id]) {
                vis[G[u][i].id] = true;
                out[u]--;
                out[G[u][i].v]--;
                dfs(G[u][i].v);
            }
        }
    }
    cout << u << endl; 
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(node(v, i));
        G[v].push_back(node(u, i)); 
        out[u]++;
        out[v]++;
    }
    dfs(1);
    return 0;
}

有向图判欧拉回路/路径

判断方法:

  • 一个有向图 \(G\) 存在欧拉路径当且仅当 \(G\) 是弱连通的有向图(将有向边全部看成无向边后该图是连通图),且满足以下两个条件之一:

    • 所有顶点的入度和出度相等;
    • 有一个顶点的出度与入度之差为 \(1\),一个顶点的出度与入度之差为 \(-1\),其余顶点的入度和出度相等。
  • 当有向图 \(G\) 包含两个入度和出度不相同的顶点且有欧拉路径时,欧拉路径必定以这两个入度出度不相同的顶点为端点。

  • 一个有向图 \(G\) 存在欧拉回路当且仅当图 \(G\) 是连通的有向图,且所有顶点的入度和出度相等。

vector<int> G[105];
int fa[105], in[105], out[105];
int n, m, f, cntout, cntin;
void init() {
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
    }
}
int get(int x) {
    if (fa[x] == x) {
        return x;
    }
    return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
    x = get(x);
    y = get(y);
    if (x != y) {
        fa[y] = x;
        f--;
    }
}

int main() {
    cin >> n >> m;
    init();
    f = n;
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        merge(u, v);
        out[u]++;
        in[v]++;
    }
    if (f != 1) {
        cout << "No" << endl;
        return 0;
    }
    for(int i=1;i<=n;i++) {
        if(out[i]-in[i]==1) {
            cntout++;
        }
        else if(out[i]-in[i]==-1) {
            cntin++;
        }
        else if(out[i]!=in[i]) {
            cout<<"No"<<endl;
            return 0;
        }
    }
    if(cntout==0 && cntin==0) {
        cout<<"Euler loop"<<endl;
    }
    else if(cntout==1 && cntin==1) {
        cout<<"Euler path"<<endl;
    }
    else {
        cout<<"No"<<endl;
    }
    return 0;
}

有向图求欧拉路径

struct node {
    int v;
    int id;
    node () {
        
    }
    node (int vv, int iid) {
        v = vv;
        id = iid;
    }
};
vector<node> G[105];
int fa[105], in[105], out[105];
int n, m, f, cntout, cntin, start;
stack<int> s;
bool vis[10005];
void init() {
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
    }
}
int get(int x) {
    if (fa[x] == x) {
        return x;
    }
    return fa[x] = get(fa[x]);
}
void merge(int x, int y) {
    x = get(x);
    y = get(y);
    if (x != y) {
        fa[y] = x;
        f--;
    }
}
int euler() {
    if (f != 1) {
        return 0;
    }
    for (int i = 1; i <= n; i++) {
        if (out[i] - in[i] == 1) {
            cntout++;
        } else if (out[i] - in[i] == -1) {
            cntin++;
        } else if (out[i] != in[i]) {
            return 0;
        }
    }
    if (cntout == 0 && cntin == 0) {
        return 1;
    } else if (cntout == 1 && cntin == 1) {
        for (int i = 1; i <= n; i++) {
            if (out[i] - in[i] == 1) {
                return i;
            }
        }
    } else {
        return 0;
    }
}
void dfs(int u) {
    if(out[u]) {
        for(int i=0;i<G[u].size();i++) {
            if(!vis[G[u][i].id]) {
                vis[G[u][i].id]=true;
                out[u]--;
                dfs(G[u][i].v);
            }
        }
    }
    s.push(u);
}
int main() {
    cin >> n >> m;
    init();
    f = n;
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(node(v, i));
        merge(u, v);
        out[u]++;
        in[v]++;
    }
    start = euler(); // 找开始的点
    if (start) {
        dfs(start);
        while(!s.empty()) {
            cout<<s.top()<<endl;
            s.pop();
        }
    } else {
        cout << "No" << endl;
    }
    return 0;
}

次小生成树

int n, m;
struct node {
    int u, v, next;
    ll w;
} a[3 * N], E[3 * N];
bool cmp(node x, node y) {
    return x.w < y.w;
}
int p[N], eid, fa[N], f[N][22], d[N];
ll mx[N][22], cmx[N][22], W;
vector<node> G[3 * N];
bool used[3 * N];
void add(int u, int v, ll w) {
    E[eid].u = u;
    E[eid].v = v;
    E[eid].next = p[u];
    E[eid].w = w;
    p[u] = eid++;
}
void init() {
    memset(mx, -1, sizeof(mx));
    memset(cmx, -1, sizeof(cmx));
    memset(p, -1, sizeof(p));
    eid = 1;
    for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) {
    if (fa[x] == x) return fa[x];
    return fa[x] = find(fa[x]);
}
void kruskal() {
    sort(a, a + m, cmp);
    for (int i = 0; i < m; i++) {
        int fu = find(a[i].u), fv = find(a[i].v);
        if (fu != fv) {
            fa[fv] = fu;
            W += a[i].w;
            add(a[i].u, a[i].v, a[i].w);
            add(a[i].v, a[i].u, a[i].w);
            used[i] = true;
        }
    }
}
void dfs(int u) {
    d[u] = d[f[u][0]] + 1;
    for (int i = p[u]; i != -1; i = E[i].next) {
        int v = E[i].v;
        if (v != f[u][0]) {
            mx[v][0] = E[i].w;
            cmx[v][0] = -INF;
            f[v][0] = u;
            dfs(v);
        }
    }
}
void init_LCA() {
    for (int i = 1; i <= n; i++) fa[i] = 0;
    dfs(1);
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i <= n; i++) {
            f[i][j] = f[f[i][j - 1]][j - 1];
            mx[i][j] = max(mx[i][j - 1], mx[f[i][j - 1]][j - 1]);
            cmx[i][j] = max(cmx[i][j - 1], cmx[f[i][j - 1]][j - 1]);
            if (mx[i][j - 1] > mx[f[i][j - 1]][j - 1]) cmx[i][j] = max(cmx[i][j], mx[f[i][j - 1]][j - 1]);
            if (mx[i][j - 1] < mx[f[i][j - 1]][j - 1]) cmx[i][j] = max(cmx[i][j], mx[i][j - 1]);
        } 
    }
}
ll LCA(int x, int y, ll w) {
    if (d[x] < d[y]) swap(x, y);
    ll maxv = -INF;
    for (int i = 20; i >= 0; i--) 
        if (d[f[x][i]] >= d[y]) {
            if (mx[x][i] != w) maxv = max(maxv, mx[x][i]);
            else maxv = max(maxv, cmx[x][i]);
            x = f[x][i];
        }
    if (x == y) return maxv;
    for (int i = 20; i >= 0; i--) {
        if (f[x][i] != f[y][i]) {
            if (mx[x][i] != w) maxv = max(maxv, mx[x][i]);
            else maxv = max(maxv, cmx[x][i]);
            if (mx[y][i] != w) maxv = max(maxv, mx[y][i]);
            else maxv = max(maxv, cmx[y][i]);
            x = f[x][i];
            y = f[y][i];
        }
    }
    if (mx[x][0] != w) maxv = max(maxv, mx[x][0]);
    else maxv = max(maxv, cmx[x][0]);
    if (mx[y][0] != w) maxv = max(maxv, mx[y][0]);
    else maxv = max(maxv, cmx[y][0]);
    return maxv;
}
ll getlen(int u, int v, ll w) {
    ll maxv = LCA(u, v, w);
    return W + w - maxv;
}
int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) cin >> a[i].u >> a[i].v >> a[i].w;
    init();
    kruskal();
    init_LCA();
    ll ans = INF;
    for (int i = 0; i < m; i++) 
        if (!used[i]) 
            ans = min(ans, getlen(a[i].u, a[i].v, a[i].w));
    cout << ans << endl;
    return 0;
}
posted @ 2021-09-03 22:59  Ryan-Liu  阅读(34)  评论(0编辑  收藏  举报