最短路径

堆优化版dij

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int maxn = 1e6+10;

int N,M,a,b,c;
int h[maxn],e[maxn],w[maxn],ne[maxn],idx;//头节点表,编号表,权值表,链表
bool vis[maxn];int dis[maxn];//访问标记数组,距离数组
priority_queue<pii,vector<pii>,greater<pii> > heap;//堆,用堆去找目前距离最短的点(未访问过)

void add(int a,int b,int c){
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}

int Dijkstra(){
    memset(dis,0x3f,sizeof dis);
    heap.push({0,1});//第一个是距离,第二个是编号
    dis[1] = 0;

    while(heap.size()){ 
        auto p = heap.top();heap.pop(); //找距离距离最短的点
        int d = p.first,s = p.second;
        if(vis[s]) continue;//如果已经访问过
        vis[s] = true;
        for(int i = h[s];i != -1;i = ne[i]){// i为地址
            int j = e[i]; //j是编号
            if(d+w[i] < dis[j]){
                dis[j] = d+w[i];
                heap.push({dis[j],j});
            }
        }
    }
    if(dis[N] == 0x3f3f3f3f) return -1;
    return dis[N];
}

树相关

求树的重心

无向图
树的重心的定义:
树若以某点为根,使得该树最大子树的结点数最小,那么这个点则为该树的重心,一棵树可能有多个重心。

树的重心的性质:
1、树上所有的点到树的重心的距离之和是最短的,如果有多个重心,那么总距离相等。

2、插入或删除一个点,树的重心的位置最多移动一个单位。

3、若添加一条边连接2棵树,那么新树的重心一定在原来两棵树的重心的路径上。

vector<int> adj[maxn];
int w[maxn]; //每个节点的权值
int sz[maxn]; //sz[u]:以u为根结点的树的权值和
int N,total;//N:总结点数,total:所有节点权值之和
int max_block = 2e9,H;//最大块的size,重心

int initsz(int u,int fa = -1){
    int sum = 0;
    for(auto v:adj[u]){
        if(v == fa) continue;
        sum += initsz(v,u);
    }
    return sz[u] = sum + w[u];
}

void findH(int u,int fa = -1){
    int mx = total-sz[u];
    for(auto v:adj[u]){
        if(v == fa) continue;
        mx = max(mx,sz[v]);
        findH(v,u);
    }
    if(mx < max_block){
        max_block = mx;
        H = u;
    }
}

求树的中心

基于树形dp
请你在树中找到一个点,使得该点到树中其他结点的最远距离最近,找到的这个点就是树的中心

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const ll maxn = 1e4+10;
const int inf = 0x3f3f3f3f;

int N;
int h[maxn],e[maxn*2],w[maxn*2],ne[maxn*2],idx = 1;
int up[maxn],down1[maxn],down2[maxn],tag1[maxn];
//某个点,往上走的最远距离,往下走的最长距离,往下走的次长距离,往下走最长距离会经过的儿子结点编号
void add(int a,int b,int c){
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}

int dfs_down(int u,int fa = -1){ //求每个结点往下走的最长和次长距离,以及最长经过的儿子编号
    int p1 = 0,p2 = 0;
    for(int i = h[u];i;i = ne[i]){
        int v = e[i],wei = w[i];
        if(v == fa) continue;
        int curp = dfs_down(v,u)+wei;
        if(curp > p1){
            p2 = p1,p1 = curp;
            tag1[u] = v;
        }else if(curp > p2){
            p2 = curp;
        }
    }
    down1[u] = p1,down2[u] = p2;//记录一下
    return p1;
}

void dfs_up(int u,int fa = -1){ //父结点更新子结点,找子结点向上的最大距离
    for(int i = h[u];i;i = ne[i]){
        int v = e[i],wei = w[i];
        if(v == fa) continue;
        if(tag1[u] == v){
            up[v] = wei + max(up[u],down2[u]);
        }else{
            up[v] = wei + max(up[u],down1[u]);
        }
        dfs_up(v,u);
    }
}

int main(){
    cin>>N;
    for(int i = 1;i<=N-1;i++){
        int a,b,c;scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    dfs_down(1);
    dfs_up(1);
    int res = inf,H = -1;
    for(int i = 1;i<=N;i++) {
        int d = max(up[i], down1[i]); //向上、向下距离取最大值
        if (d < res) {
            res = d; //中心能移动的最大距离
            H = i;//树的中心
        }
    }

    return 0;
}

倍增法求LCA

无向图
一篇不错的LCA教程

int h[maxn],ne[maxn*2],e[maxn*2],idx = 1;
int q[maxn],fa[maxn][21],dep[maxn];
void add(int a,int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void bfs(int root){
    int hh = 0,tt = -1;
    q[++tt] = root;
    dep[0] = 0,dep[root] = 1;

    while(hh<=tt){
        int u = q[hh++];
        for(int i = h[u];i;i = ne[i]){
            int v = e[i];
            if(dep[v]) continue;
            dep[v] = dep[u]+1;
            q[++tt] = v;
            fa[v][0] = u;
            for(int k = 1;k<=20;k++){
                fa[v][k] = fa[fa[v][k-1]][k-1];
            }
        }
    }
}

int LCA(int x,int y){
    if(dep[x] < dep[y]) swap(x,y);
    for(int k = 20;k>=0;k--){
        if(dep[fa[x][k]] >= dep[y])
            x = fa[x][k];
    }
    if(x == y) return x;
    for(int k = 20;k>=0;k--){
        if(fa[x][k] != fa[y][k]){
            x = fa[x][k];
            y = fa[y][k];
        }
    }
    return fa[x][0];
}

求树的直径

无向图,可以有负边
基于树形dp

int N,d; // 结点数,直径
vector<pii> adj[maxn];
int dfs(int u = 1,int fa = -1){
    int p1 = 0,p2 = 0; //以u结点往下走的最长路径和次长路径
    for(auto p:adj[u]){
        int v = p.first,w = p.second;
        if(v == fa) continue;
        int curp  = max(0,dfs(v,u) + w); //如果是无权树,w = 1
        if(curp > p1) p2 = p1,p1 = curp;
        else if( curp > p2) p2 = curp;
    }
    d = max(d,p1 + p2);
    return p1;
}

图相关

染色法判断二分图

bool ok;
void DFS(int s){ 
    if(!ok) return ;
    for(int i = 0;i<h[s].size();i++){
        int j = h[s][i];
        if(col[j] == -1){
            col[j] = col[s]^1;//染和s结点相反的颜色
            DFS(j);
        }else if(col[j] == col[s]){ //如果已经和结点s颜色相同了,那么必定不是二分图
            ok = false;
            return;
        }
    }
}
memset(col,-1,sizeof col);//初始化染色标记数组
for(int i = 1;i<=N && ok;i++){ //对每个未染色点都进行DFS,因为可能有多个联通分量
    if(col[i] == -1){
        col[i] = 0; 
        DFS(i);
    }
}
然后是否ok