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

    ( ext{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]<<"
    ";
    	return 0;
    }
    

    ( ext{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;
    }
    

    ( ext{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]);
                }
            }
        }
    }
    

    ( ext{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<<"
    ";
    	return 0;
    }
    

    ( ext{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;
    }
    

    ( ext{ST})算法

    RMQ问题,求区间最值。

    时间复杂度:

    • 预处理: (O(nlog 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;
    }
    

    ( ext{LCA})

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

    时间复杂度:

    • 预处理:(O(nlog 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;
    }
    

    割点

    ( ext{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;
    }
    

    点双连通分量

    ( ext{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;
    }
    

    边双连通分量

    ( ext{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;
    }
    

    强连通分量

    ( ext{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- ext{SAT}) 问题

    (2- ext{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
    ", 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;
    }
    
  • 相关阅读:
    基本排序算法
    Ubuntu下fcitx安装。(ibus不会用)
    sublime搭建Java编译平台及编码问题
    Centos6.5(final)安装gcc和g++,python以及导致问题的解决方法
    如何查询centos查看系统内核版本,系统版本,32位还是64位
    vim插件之SnipMate
    Linux rename命令
    Hadoop安装(Ubuntu Kylin 14.04)
    vim 文字插入
    Checkbox indeterminate属性
  • 原文地址:https://www.cnblogs.com/Ryan-juruo/p/15225330.html
Copyright © 2020-2023  润新知