大板子 —— 4

——— 图论 ———

—— Dijkstra ——
最短路模板 —— Dijkstra+堆优化(高级版)
const ll inf = (ll)1e16;
int n, m; bool vis[N]; ll dis[N];
vector <pii> g[N];

struct Node{
	int id; ll d;
	Node() {}
	Node(int id, ll d):id(id),d(d){}
	bool operator < (const Node &A) const { return d > A.d; }
};

void dijkstra(int st){
	for(int i=1; i<=n; i++){
		vis[i] = 0; dis[i] = inf;
	}
	dis[st] = 0;
	priority_queue <Node> Q;
	Q.push(Node(st, 0));
	Node nd;
	while(!Q.empty()){
		nd = Q.top(); Q.pop();
		if(vis[nd.id]) continue;  vis[nd.id] = true;
		for(int i=0; i<g[nd.id].size(); i++){
			int j = g[nd.id][i].first;
			int k = g[nd.id][i].second;
			if(nd.d + k < dis[j] && !vis[j]){
				dis[j] = nd.d + k;
				Q.push(Node(j, dis[j]));
			}
		}
	}
}

Dijkstra + 配对堆(高级):

#include<ext/pb_ds/priority_queue.hpp>
using namespace __gnu_pbds;
const int mn = 100005;
const int maxn = 200005 ;
const int inf = 2147483647;
typedef __gnu_pbds::priority_queue< pair<int,int> ,\
greater< pair<int,int> >,pairing_heap_tag > heap;
heap::point_iterator id[mn];//记录下每个点的迭代器 
heap q;

struct edge{int to, next, dis;};
edge e[maxn * 2];
int head[mn], edge_max;
int n, m, st, dis[mn];

void add(int x, int y, int z){
    e[++edge_max].to=y;
    e[edge_max].dis=z;
    e[edge_max].next=head[x];
    head[x]=edge_max;
}

void dij(int x){
    for(int i=1; i<=n; i++) dis[i] = inf;  dis[x]=0;
    id[x] = q.push(make_pair(0, x));//每次push会返回新加入点的迭代器 
    while(!q.empty()){
        int now = q.top().second;  q.pop();
        for(int i=head[now]; i; i=e[i].next){
            if(e[i].dis+dis[now] < dis[e[i].to]){
                dis[e[i].to] = dis[now]+e[i].dis;
                if(id[e[i].to]!=0) //如果在堆中 
                    q.modify(id[e[i].to], make_pair(dis[e[i].to], e[i].to));
                else id[e[i].to] = q.push(make_pair(dis[e[i].to], e[i].to));
            }
        }
    }
}

分层图
const ll inf = (ll)1e16;
int n, m, k, st, ed;
vector <pii> V[N];
bool vis[N][12];
ll dis[N][12];

struct Node {
	int id, step; ll d;
	Node() {}
	Node(int id, int step, ll d):id(id),step(step),d(d) {}
	bool operator < (const Node &A)const { return d > A.d; }
};

void dijkstra(int st) {
	for(int i=1; i<=n; ++i) 
		for(int j=0; j<=k; ++j)
			dis[i][j] = inf, vis[i][j] = 0;;

	dis[st][0] = 0;
	priority_queue<Node> Q;
	Q.push(Node(st, 0, 0));
	Node nd;

	while(!Q.empty()) {
		nd = Q.top(); Q.pop();
		if(nd.id == ed) break;
		if(vis[nd.id][nd.step]) continue;
		vis[nd.id][nd.step] = true;
		
		for(int i=0; i<V[nd.id].size(); ++i) {
			int j = V[nd.id][i].first;
			int len = V[nd.id][i].second;
			int step = nd.step;
			if(dis[nd.id][step] + len < dis[j][step]){
				dis[j][step] = dis[nd.id][step] + len;
				Q.push(Node(j, step, dis[j][step]));
			}
			if(step == k) continue;
			if(dis[nd.id][step] < dis[j][step+1]){
				dis[j][step+1] = dis[nd.id][step];
				Q.push(Node(j, step+1, dis[j][step+1]));
			}
		}
	}
}

Johnson全源最短路

用于求边权带负的全源最短路
d i s i , j dis_{i,j} disi,j 为从 i i i j j j 的最短路,在第 i i i 行输出 ∑ j = 1 n j × d i s i , j \sum\limits_{j=1}^n j\times dis_{i,j} j=1nj×disi,j

struct node {
	int dis, id;
	inline bool operator < (const node &x) const { return dis > x.dis; } 
	node (int x, int y) { dis = x, id = y; }
} ; 
bool vis[maxn];
int n, m, in[maxn];
ll h[maxn], dis[maxn];
vector<pair<int, int> > G[maxn];

inline bool SPFA(int s) { //从源点开始求一遍最短路 , 记为 h
	queue <int> q; 
	memset(h, 0x3f, sizeof(h));
	h[s] = 0; vis[s] = 1; q.push(s);
	while(!q.empty()) {
		int u = q.front(); q.pop(); vis[u] = 0;
		for(int i=0; i<G[u].size(); i++) {
			int v = G[u][i].first, w = G[u][i].second;
			if(h[v] > h[u]+w) {
				h[v] = h[u] + w;
				if(!vis[v]) {
					vis[v] = 1; q.push(v);
					if(++in[v] >= n) return 0;
				}
			}
		}
	} return 1;
}

inline void dijkstra(int s) { //从每个点开始走一遍最短路模板
	priority_queue <node> q; 
	memset(vis, 0, sizeof(vis));
	for(int i=1; i<=n; i++) dis[i] = (i==s) ? 0 : (INF);
	q.push(node(0, s)); 
	while(!q.empty()) {
		int u = q.top().id; q.pop(); 
		if(vis[u]) continue; vis[u] = 1; 
		for(int i=0; i<G[u].size(); i++) {
			int v = G[u][i].first, w = G[u][i].second;
			if(dis[v] > dis[u]+w) {
				dis[v] = dis[u] + w;
				if(!vis[v]) q.push(node(dis[v], v));
			}
		}
	}
}

int main() {
	scanf("%d%d", &n, &m);
	while(m--) {
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		G[u].push_back(make_pair(v, w));
//		G[v].push_back(make_pair(u, w));
	} 
	for(int i=1; i<=n; i++) G[0].push_back(make_pair(i, 0)); //创造上帝视角
	if(!SPFA(0)) return puts("-1"), 0; //负环结束
	for(int u=1; u<=n; u++)
		for(int i=0; i<G[u].size(); i++) 
			G[u][i].second += h[u] - h[G[u][i].first]; //重构
	for(int i=1; i<=n; i++) {
		dijkstra(i); ll ans = 0;
		for(int j=1; j<=n; j++)
			ans += (dis[j]==INF) ? (j*INF) : (j*(dis[j]+h[j]-h[i]));
		printf("%lld\n", ans);	//跑最短路并统计答案
	}	
}

—— SPFA ——
struct EDGE {
	int next; int to; ll w;
} edge[MAXM];
 
int n, m, st, ed, cnt, pre[MAXN];
int head[MAXN], num[MAXN];
ll dis[MAXN]; bool vis[MAXN];
queue<int> Q;
 
void Add(int u, int v, ll w) {
	edge[++cnt].next = head[u];
	edge[cnt].to = v;
	edge[cnt].w = w;
	head[u] = cnt;
}
 
bool SPFA(int x) {
	while(!Q.empty()) Q.pop();
	for(int i=1; i<=n; i++) dis[i] = ANS_MAX;
	dis[x] = 0; num[x] = 1; Q.push(x); vis[x] = true;
	while(!Q.empty()) {
		int k = Q.front(); Q.pop();
		vis[k] = false;
		if(dis[k] == ANS_MAX) continue;
		for(int i=head[k]; i!=0; i=edge[i].next) {
			int j = edge[i].to;
			if(dis[j] > dis[k] + edge[i].w) {
				dis[j] = dis[k] + edge[i].w;
				num[j] = num[k]+1; pre[j] = k;
				//if(num[j]>n) return 1;	//判断负环 
				if(!vis[j]) {
					Q.push(j);
					vis[j] = true;
				}
			}
		}
	}
	return 0;
}
 
void print_path(int end) { //打印最短路的路径
	stack <int> path;
	int now = end;
	while(1) { //前驱
		path.push(now);
		if(now == st) break;
		now = pre[now];
	}
	while(!path.empty()) {
		now = path.top();
		path.pop();
		if(path.empty()) printf("%d\n", now);
		else printf("%d-->", now);
	}
}
 
int main() {
	int cas; cas = Read();
	while(cas--){
		memset(head, 0, sizeof(head));
		memset(pre, -1, sizeof(pre));
		cnt = 0; read(); SPFA(st);
		//SPFA(1) ? printf("YES\n") : printf("NO\n");
		for(int i=1; i<=n; i++) printf("%lld ", dis[i]);
		printf("\n");
		//for(int i=1; i<=n; i++) print_path(i);
	}
}

—— Bellman_Ford ——
int n, m, st; //点,边,起点
typedef struct Edge { //边
	int u, v, w;
} Edge;

Edge edge[N];
int dis[N], pre[N];

bool Bellman_Ford() {
	for(int i=1; i<=n; ++i) //初始化
		dis[i] = (i == st ? 0 : MAX);
	for(int i=1; i<=n-1; ++i){
		bool flag = false;
		for(int j = 1; j <= m; ++j)
			if(dis[edge[j].v] > dis[edge[j].u] + edge[j].w) { 
				dis[edge[j].v] = dis[edge[j].u] + edge[j].w;
				flag = true;
			}
		if(!flag) return true;//没有负环回路
	}
		
	bool flag = 1; //判断是否含有负权回路
	for(int i=1; i<=m; ++i)
		if(dis[edge[i].v] > dis[edge[i].u] + edge[i].w) {
			flag = 0; break;
		}
	return flag;
}

—— Floyd ——
const int INF = INT_MAX/100;
int n, m, d[3000][3000];
void floyed() {
	for(int k=0; k<n; k++) {                             
		for(int i=0; i<n; i++) {
			for(int j=0; j<n; j++) {
					if(d[i][k]<INF && d[k][j]<INF)       
					d[i][j]=min(d[i][j], d[i][k]+d[k][j]);  
			}
		}
	}
}
void init() {
	scanf("%d%d", &n, &m);
	for(int i=0; i<n; i++) {
		for(int j=0; j<n; j++)
			if(i==j) d[i][j]=0;
			else d[i][j]=INF;
	}
	for(int i=0; i<m; i++) {
		int x, y, z;
		scanf("%d%d%d", &x, &y, &z);
		d[x-1][y-1]=z;
	}
}

—— Tarjan ——

tarjan缩点

大意:求一条路径,点权和最大
题解:缩点后DP,也可以DFS

int n, m, tot, top, ans, numc;
int dfn[maxn], low[maxn], st[maxn], a[maxn];
int vis[maxn], col[maxn], cnt[maxn], sum[maxn];
vector <int> g1[maxn], g2[maxn];

void tarjan(int u){
	dfn[u] = low[u] = ++tot;
	st[++top] = u;
	vis[u] = 1;
	for(auto v : g1[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else if(vis[v]) low[u] = min(low[u], dfn[v]);
	} 
	if(dfn[u] == low[u]){
		numc++;
		while(st[top+1] != u){
			col[st[top]] = numc;
			sum[numc] += a[st[top]];
			vis[st[top--]] = 0;
		}
	}
}

int dfs(int u, int fa){
	int ret = sum[u], mx = 0;
	for(auto v : g2[u]){
		if(v == fa) continue;
		mx = max(mx, dfs(v, u));
	}
	return ret + mx;
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) scanf("%d", a+i);
	for(int i=1, u, v; i<=m; i++) {
		scanf("%d%d", &u, &v);
		g1[u].push_back(v);
	}
	for(int i=1; i<=n; i++)
		if(!dfn[i]) tarjan(i);
	for(int i=1; i<=n; i++)
		for(auto v : g1[i])
			if(col[i] ^ col[v])
				g2[col[i]].push_back(col[v]);
	for(int i=1; i<=numc; i++)
		ans = max(ans, dfs(i, 0));
	printf("%d\n", ans);
}

tarjan求割点、桥

int n, m, tot, ans;
int dfn[maxn], low[maxn], iscut[maxn];
vector <int> g[maxn];
vector <pii> bri;

void tarjan(int u, int fa){
	dfn[u] = low[u] = ++tot;
	int child = 0;
	for(auto v : g[u]){
		if(v == fa) continue;
		if(!dfn[v]){
			child++;
			tarjan(v, u);
			if(low[v] >= dfn[u]) iscut[u] = 1;
			if(low[v] > dfn[u]) bri.push_back({min(u, v), max(u, v)});
			low[u] = min(low[u], low[v]);
		} else low[u] = min(low[u], dfn[v]);
	}
	if(!fa && child==1) iscut[u] = 0;
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i=1, u, v; i<=m; i++){
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);		
	}
	for(int i=1; i<=n; i++)
		if(!dfn[i]) tarjan(i, 0);
	for(int i=1; i<=n; i++)
		if(iscut[i]) ans++;
	printf("%d\n", ans);
	for(int i=1; i<=n; i++)
		if(iscut[i]) printf("%d ", i);
//	printf("%d\n", bri.size());
//	sort(bri.begin(), bri.end());
//	for(auto i : bri) 
//		printf("%d %d\n", i.first, i.second);
}

2 - sat

介绍

n n n 个变量 a i a_i ai,每个变量能且只能选 0 / 1 0/1 0/1
给出若干条件,形如: ( n o t ) (not) (not) a i a_i ai o p t opt opt ( n o t ) (not) (not) a j = 0 / 1 , o p t = a n d , o r , x o r a_j = 0/1,opt = and,or,xor aj=0/1opt=andorxor
求出满足所有限制的一组 a a a


做法

每个点拆成 i i i i + n i+n i+n,分别表示选取和不选取,下面用 i ′ i' i 表示 i + n i+n i+n
定义有向边 u → v u \rightarrow v uv,表示选择了 u u u 就必须选择 v v v
然后对所有关系连边,包括逆否命题的连边:

  • i , j i,j i,j 不能同时选,即选了 i i i 就要选 j ′ : i → j ′ 、 j → i ′ a i j':i \rightarrow j'、j \rightarrow i' \quad a_i jijjiai x o r xor xor a j = 1 a_j = 1 aj=1
  • i , j i,j i,j 必须同时选,即选了 i i i 就要选 j : i → j 、 j → i a i j:i \rightarrow j、j \rightarrow i \qquad a_i jijjiai x o r xor xor a j = 0 a_j = 0 aj=0
  • i , j i,j i,j 至少选一个,即选了 i ′ i' i 就要选 j : i ′ → j 、 j ′ → i a i j:i' \rightarrow j、j' \rightarrow i \quad a_i jijjiai o r or or a j = 1 a_j = 1 aj=1
  • i i i 必须选: i ′ → i , a i = 1 i' \rightarrow i,a_i = 1 iiai=1

那么对于一个强联通分量里的点,肯定是全选或全不选
t a r j a n tarjan tarjan 缩点后,即可选出可行解,对于每个变量 x x x 有四种情况:

  • x x x ¬ x \neg x ¬x 毫无关系:任意取
  • x ⇒ ¬ x x \Rightarrow \neg x x¬x:取 x x x 为假
  • ¬ x ⇒ x \neg x \Rightarrow x ¬xx:取 x x x 为真
  • x ⇒ ¬ x x \Rightarrow \neg x x¬x 并且 ¬ x ⇒ x \neg x \Rightarrow x ¬xx:无解

那么缩点后,若 x x x ¬ x \neg x ¬x 在同个强联通分量里则无解, 否则选取拓扑序较大的那么点
但是注意: t a r j a n tarjan tarjan 里的染色顺序是逆拓扑,我们可以直接用这个染色顺序来求解
也就是若 col[i] < col[i+n],则直接选择 i i i


求字典序最小的解

这里用暴力 D F S DFS DFS,实际复杂度是 n 2 n^2 n2

int n, m, top, vis[maxn], st[maxn];
vector <int> g[maxn];

bool dfs(int u) {
	if(vis[u^1]) return false;
	if(vis[u]) return true;
	vis[u] = 1;
	st[++top] = u;
	for(auto v : g[u])
		if(!dfs(v)) return false;
	return true;
}

bool solve() {
	for(int i=0; i<n<<1; i+=2) {
		if(!vis[i] && !vis[i+1]) {
			top = 0;
			if(!dfs(i)) {
				while(top) vis[st[top--]] = 0;
				if(!dfs(i+1)) return false;
			}
		}
	}
	return true;
}

int main() {
	while(~scanf("%d%d", &n, &m)) {
		memset(vis, 0, sizeof(vis));
		for(int i=0; i<n<<1; i++) g[i].clear();
		for(int i=1, a, b; i<=m; i++) {
			scanf("%d%d", &a, &b);
			a--, b--;
			g[a].push_back(b ^ 1);
			g[b].push_back(a ^ 1);
		}
		if(!solve()) puts("NIE");
		else
			for(int i=0; i<n<<1; i++)
				if(vis[i]) printf("%d\n", i + 1);
	}
}

—— 支配树 ——

    对于有向无环图的支配树
    按照拓扑排序建树
    每个节点在支配树上的父亲
    是原图上所有父亲的 L C A LCA LCA
    时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

int n, m, cnt, ans[maxn];
int in[maxn], tp[maxn];
int f[maxn][25], dep[maxn];
vector <int> g[maxn], t[maxn], fa[maxn];

int lca(int x, int y){
	if(dep[x] < dep[y]) swap(x, y);
	for(int i=20; ~i; i--)
		if(dep[f[x][i]] >= dep[y])
			x = f[x][i];
	if(x == y) return x;
	for(int i=20; ~i; i--)
		if(f[x][i] ^ f[y][i])
			x=f[x][i], y=f[y][i];
	return f[x][0];
}

void build(int x){
	int lcaf = fa[x][0];
	for(int i=1; i<fa[x].size(); i++)
		lcaf = lca(lcaf, fa[x][i]);
	t[lcaf].push_back(x);
	dep[x] = dep[lcaf] + 1;
	f[x][0] = lcaf;
	for(int i=1; i<=20; i++)
		f[x][i] = f[f[x][i-1]][i-1];
}

void tp_sort(){
	queue <int> Q;
	for(int i=1; i<=n; i++)
		if(!in[i]) {
			in[i]++;
			g[0].push_back(i);
			fa[i].push_back(0);
		} 
	Q.push(0);
	while(!Q.empty()){
		int q = Q.front(); Q.pop();
		tp[cnt++] = q;
		for(auto i : g[q]){
			in[i]--;
			if(!in[i]) {
				Q.push(i);
				build(i);
			}
		}
	}
}

int main() {
	scanf("%d", &n);
	for(int i=1, x; i<=n; i++){
		while(1){
			scanf("%d", &x);
			if(x==0) break;
			g[x].push_back(i);
			fa[i].push_back(x);
			in[i]++;
		}
	}
	tp_sort(); dfs(0);	
}

—— 二分图 ——
二分图定理 :
  • 最小点覆盖 == 最大匹配
  • 最大独立集 == 顶点数 - 最大匹配
  • 最小边覆盖 == 顶点数 - 最大匹配
  • 最小路径覆盖 == 顶点数 - 最大匹配
  • 最大团 == 补图的最大独立集
  • 最小割 == 最小点权覆盖集 = 点权和 - 最大点权独立集 = 最大流

  • 1、匈牙利

    时间复杂度: O ( n ∗ m ) O(n*m) O(nm)

    const int maxn = 1e3 + 5;
    vector <int> g[maxn];	// 1 ~ n
    int n, m, lik[maxn];
    bool vis[maxn];
    
    bool dfs(int x) {
    	for(int i=0; i<g[x].size(); i++) {
    		int y = g[x][i];
    		if(vis[y]) continue;
    		vis[y] = true;
    		if(lik[y]==-1 || dfs(lik[y])) {
    			lik[y] = x;
    			return true;
    		}
    	}
    	return false;
    }
    int hungry() {
    	int ret = 0;
    	memset(lik, -1, sizeof(lik));
    	for(int i=1; i<=n; i++) {	// n为左边顶点数 
    		memset(vis, 0, sizeof(vis));
    		if(dfs(i)) ret++;
    	}
    	return ret;
    }
    

    2、Hopcroft-Karp

    时间复杂度: O ( n ∗ m ) O(\sqrt{n}*m) O(n m)

    const int maxn = 1e3 + 5;
    int n, m, dis, vis[maxn];
    int mx[maxn], my[maxn], dx[maxn], dy[maxn];
    vector<int> g[maxn];
    
    int bfs() {
    	dis = INT_MAX;
    	memset(dx, -1, sizeof(dx));
    	memset(dy, -1, sizeof(dy));
    	queue <int> Q;
    	for(int i=1; i<=n; i++)
    		if(mx[i] == -1) {
    			Q.push(i);
    			dx[i] = 0;
    		}
    	while(!Q.empty()) {
    		int x = Q.front(); Q.pop();
    		if(dx[x] > dis) break;
    		for(int i=0; i<g[x].size(); i++) {
    			int y = g[x][i];
    			if(dy[y] != -1) continue;
    
    			dy[y] = dx[x]+1;
    			if(my[y] == -1) dis = dy[y];
    			else {
    				dx[my[y]] = dy[y]+1;
    				Q.push(my[y]);
    			}
    		}
    	}
    	return dis != INT_MAX;
    }
    int dfs(int x) {
    	for(int i=0; i<g[x].size(); i++) {
    		int y = g[x][i];
    		if(vis[y] || dy[y]!=dx[x]+1) continue;
    		vis[y] = true;
    		if(my[y]!=-1 && dy[y]==dis) continue;
    		if(my[y]==-1 || dfs(my[y])) {
    			my[y] = x; mx[x] = y;
    			return 1;
    		}
    	}
    	return 0;
    }
    int hungry_fast() {
    	int ret = 0;
    	memset(my, -1, sizeof(my));
    	memset(mx, -1, sizeof(mx));
    	while(bfs()) {
    		memset(vis, 0, sizeof(vis));
    		for(int i=1; i<=n; i++)
    			if(mx[i]==-1 && dfs(i)) ret++;
    	}
    	return ret;
    }
    

    —— 网络流 ——
    Dinic
    int n, m, st, ed;
    ll ans, dis[maxn];
    int cnt, head[maxn], cur[maxn];
    
    struct EDGE {
    	int nxt, to;  ll w;
    } edge[maxn];
    
    void add(int u, int v, ll w) {
    	edge[cnt].nxt = head[u];
    	edge[cnt].w = w;
    	edge[cnt].to = v;
    	head[u] = cnt++; // 从0开始 
    }
    
    int bfs() {
    	memset(dis, -1, sizeof(dis));
    	dis[st] = 0;
    	queue <int> Q;
    	Q.push(st);
    	while(!Q.empty()) {
    		int u = Q.front(); Q.pop();
    		for(int i=head[u]; ~i; i=edge[i].nxt) {
    			int v = edge[i].to;
    			if(dis[v]==-1 && edge[i].w>0) {
    				dis[v] = dis[u] + 1;
    				Q.push(v);
    			}
    		}
    	}
    	return dis[ed] != -1;	// 判断是否联通。
    }
    
    ll dfs(int u, ll exp) {
    	if(u == ed) return exp;		// 到达终点,全部接受。
    	ll flow = 0, tmp = 0;
    	for(int &i=cur[u]; ~i; i=edge[i].nxt) {	// 当前弧优化 
    		int v = edge[i].to;
    		if(dis[v]==dis[u]+1 && edge[i].w>0) {
    			tmp = dfs(v, min(exp, edge[i].w));	
    			if(!tmp) continue;
    			exp -= tmp;			// 流量限制-流量,后边有判断。
    			flow += tmp;
    
    			edge[i].w -= tmp;		// 路径上的边残量减少
    			edge[i^1].w += tmp;		// 流经的边的反向边残量增加。
    			if(!exp) break;			// 判断是否在限制边缘
    		}
    	}
    	return flow;
    }
    
    int main() {
    	while(~scanf("%d%d%d%d", &n, &m, &st, &ed)) {
    		cnt = ans = 0;
    		memset(head, -1, sizeof(head));
    		int x, y; ll z;
    		for(int i=1; i<=m; i++) {
    			scanf("%d%d%lld", &x, &y, &z);
    			add(x, y, z);
    			add(y, x, 0);	// 相邻建边。
    		}
    		while(bfs()) {
    			for(int i=0; i<=n; i++) cur[i] = head[i];
    			ans += dfs(st, INF);
    		}
    		printf("%lld\n", ans);
    	}
    }
    

    费用流
    struct edge {
    	int to, cap, cost, rev;
    	edge() {}
    	edge(int _to, int _cap, int _cost, int _rev):\
    		to(_to), cap(_cap), cost(_cost), rev(_rev) {}
    };
    
    struct Min_Cost_Max_Flow {
    	int V, h[maxn], dis[maxn], prev[maxn], pree[maxn];
    	vector <edge> G[maxn];
    	void init(int n) {	// 调用前初始化
    		V = n;
    		for(int i=0; i<=V; ++i) G[i].clear();
    	}
    	void add_edge(int from, int to, int cap, int cost) {
    		G[from].push_back(edge(to, cap, cost, G[to].size()));
    		G[to].push_back(edge(from, 0, -cost, G[from].size()-1));
    	}
    	// flow是自己传进去的变量,就是最后的最大流,返回的是最小费用
    	int min_cost_max_flow(int s, int t, int f, int &flow) {
    		int res = 0; fill(h, h+V+1, 0);
    		while(f) {
    			priority_queue<pii, vector<pii>, greater<pii> > q;
    			fill(dis, dis+V+1, INF);
    			dis[s] = 0; q.push(pii(0, s));
    			while(!q.empty()) {
    				pii now = q.top(); q.pop();
    				int v = now.second;
    				if(dis[v] < now.first) continue;
    				for(int i=0; i<G[v].size(); ++i) {
    					edge &e = G[v][i];
    					if(e.cap>0 && dis[e.to]>dis[v]+ \
    							e.cost+h[v]-h[e.to]) {
    						dis[e.to] = dis[v]+e.cost+h[v]-h[e.to];
    						prev[e.to] = v; pree[e.to] = i;
    						q.push(pii(dis[e.to], e.to));
    					}
    				}
    			}
    			if(dis[t] == INF) break;
    			for(int i=0; i<=V; ++i) h[i] += dis[i];
    			int d = f;
    			for(int v=t; v!=s; v=prev[v]) 
    				d = min(d, G[prev[v]][pree[v]].cap);
    			f -= d; flow += d; res += d * h[t];
    			for(int v=t; v!=s; v=prev[v]) {
    				edge &e = G[prev[v]][pree[v]];
    				e.cap -= d; G[v][e.rev].cap += d;
    			}
    		}
    		return res;
    	}
    } MCMF;
    
    int n, m, s, t;
    int from, to, cap, cost, flow;
    int main() {
    	scanf("%d%d%d%d", &n, &m, &s, &t);
    	MCMF.init(n);
    	for(int i=0; i<m; ++i) {
    		scanf("%d%d%d%d", &from, &to, &cap, &cost);
    		MCMF.add_edge(from, to, cap, cost);
    	}
    	printf("%d %d\n", flow, MCMF.min_cost_max_flow(s, t, INF, flow));
    }
    

    网格图网络流 —— 平面图转对偶图

    对偶图

    一个图的对偶图如下:

    黑点为原图,红点为对偶图
    平面图每一个面是对偶图的每一个点
    平面图中面与面的割线是对偶图的边
    若平面图中某一条边只属于一个面,那么在对偶图中就是一个环边
    平面图周围无边界的面也是对偶图中的一个点


    网格图网络流

    如图,要求 ( 1 , 1 ) (1, 1) (1,1) ( n , m ) (n, m) (n,m) 的最小割
    一条割边相当于一条杠,在网格图网络流中,源点和汇点不连通后,这些杠是连续的
    因此,就可以在割线上跑最短路,边权即为原图的权值
    然后要设最短路的源点和汇点,这里我们要使得左上至右下不连通,因此源点和汇点分别在左下和右上


    题目即为上述的图,注意这里不是严格的网格图,但是同样具有网格图网络流的性质
    因此将每个小三角当成一个点建图即可

    int n, m, cnt, st, ed;
    int vis[maxn], head[maxn];
    ll dis[maxn];
    
    int main(){
    	scanf("%d%d", &n, &m);  int w;
    	st = 0, ed = (n - 1) * (m - 1) * 2 + 1;
    	memset(head, -1, sizeof(head));
    	for(int i=1, tot=2; i<=n; i++)
    		for(int j=1; j<m; j++, tot+=2) {
    			scanf("%d", &w);
    			int u = i == 1 ? ed : tot - m * 2 + 1, v = i == n ? 0 : tot;
    			add(u, v, w), add(v, u, w);
    		}
    	for(int i=1, tot=1; i<n; i++){
    		for(int j=1; j<m; j++, tot+=2) {
    			scanf("%d", &w);
    			int u = j == 1 ? 0 : tot - 1, v = tot;
    			add(u, v, w), add(v, u, w);
    		}
    		scanf("%d", &w);
    		add(tot-1, ed, w), add(ed, tot-1, w);
    	}
    	for(int i=1, tot=1; i<n; i++)
    		for(int j=1; j<m; j++, tot+=2) {
    			scanf("%d", &w);
    			add(tot, tot+1, w), add(tot+1, tot, w);
    		}
    	dijkstra(st);
    	printf("%lld", dis[ed]);
    }
    

    —— 拓扑排序 ——
    int n, m, cnt, in[maxn], tp[maxn];
    vector <int> v[maxn];
    
    void tp_sort(){
    	queue <int> Q;
    	for(int i=1; i<=n; i++)
    		if(!in[i]) Q.push(i);
    	while(!Q.empty()){
    		int q = Q.front(); Q.pop();
    		tp[cnt++] = q;
    		for(auto i : v[q]){
    			in[i]--;
    			if(!in[i]) Q.push(i);
    		}
    	}
    }
    
    int main() {
    	scanf("%d%d", &n, &m);
    	for(int i=0, x, y; i<m; i++){
    		scanf("%d%d", &x, &y);
    		v[x].push_back(y);
    		in[y]++;
    	}
    	tp_sort();
    	for(int i=0; i<cnt; i++)  printf("%d ", tp[i]);
    }
    

    —— 树链剖分 ——
    树的重心

    概念

    以树的重心为整棵树的根时,它的最大子树最小(也就是删除该点后最大联通块最小)

    定义及性质

    定义1:找到一个点,删除它得到的森林中最大的子树节点数最少,那么这个点就是这棵树的重心
    定义2:删除重心后得到的所有子树,其顶点数必然不超过 n / 2 n/2 n/2
    性质1:树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样
    性质2:把两个树通过一条边相连得到一个新的树,那么新的树的重心在连接原来两个树的重心的路径上
    性质3:把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离


    求法

    s z [ u ] sz[u] sz[u] 表示以 u u u 为根的子树总大小(包括根)
    m a x _ p a r t max\_part max_part表示最大子树的大小
    n − s z [ u ] n - sz[u] nsz[u] 表示删除 u u u 后上方联通块的大小


    模板题,求表示重心的所有的子树中最大的子树的结点数目

    int n, sz[maxn], ans_id, ans_num = maxn;
    vector <int> g[maxn];
    
    void dfs(int u, int fa){
    	sz[u] = 1; int max_part = 0;
    	for(auto v : g[u]){
    		if(v == fa) continue;
    		dfs(v, u); sz[u] += sz[v];
    		max_part = max(max_part, sz[v]);
    	}
    	max_part = max(max_part, n - sz[u]);
    	if(max_part < ans_num){
    		ans_num = max_part;
    		ans_id = u;
    	}
    }
    
    int main() {
    	scanf("%d", &n);
    	for(int i=1, u, v; i<n; i++){
    		scanf("%d%d", &u, &v);
    		g[u].push_back(v); g[v].push_back(u);
    	}
    	dfs(1, 0);
    	printf("%d\n", ans_num);
    }
    

    题意:一颗有根树,对于每次询问 v v v,判断以 v v v 为根的子树重心是哪个
    题解:
               a n s [ u ] ans[u] ans[u] 表示以 u u u 为根的子树重心, m a x _ i d max\_id max_id 表示重儿子
              根据重心定义2:删除重心后得到的所有子树,其顶点数必然不超过 n / 2 n/2 n/2
              则判断节点 u u u 是否为以 u u u 为根的树的重心: s z [ m a x _ i d ] ∗ 2 ≤ s z [ u ] sz[max\_id]*2 ≤ sz[u] sz[max_id]2sz[u]

              若 u u u 本身不是重心,则考虑重心的转移,根据性质2:
              以 u u u 为根的重心,一定在以 m a x _ i d max\_id max_id 为根的重心 到 u u u 的路径上,即 a n s [ m a x _ i d ] ans[max\_id] ans[max_id] u u u
              或者说:如果 m a x _ i d max\_id max_id s i z e ∗ 2 > s z [ u ] size*2>sz[u] size2sz[u],则重心一定在这个子树中
              由于 s z [ a n s [ m a x _ i d ] ] sz[ans[max\_id]] sz[ans[max_id]] 一定小于 s z [ u ] / 2 sz[u] / 2 sz[u]/2,则只需要判断另一边即可
              注意枚举重心转移的总时间为 O ( n ) O(n) O(n),则总时间复杂度: O ( n ) O(n) O(n)

    int n, q, ans[maxn], f[maxn], sz[maxn];
    vector <int> g[maxn];
    
    void dfs(int u, int fa){
    	sz[u] = 1, ans[u] = u;
    	int max_id = 0;
    	for(auto v : g[u]){
    		if(v == fa) continue;
    		dfs(v, u); sz[u] += sz[v];
    		if(sz[v] > sz[max_id]) max_id = v;
    	}
    	if(sz[max_id]*2 > sz[u]) {
    		int tmp = ans[max_id];
    		while((sz[u]-sz[tmp])*2 > sz[u]) tmp = f[tmp];
    		ans[u] = tmp;
    	}
    }
    
    int main() {
    	scanf("%d%d", &n, &q);
    	for(int i=2, u; i<=n; i++){
    		scanf("%d", &u); f[i] = u;
    		g[u].push_back(i); g[i].push_back(u);
    	}
    	dfs(1, 0);
    	while(q--){
    		int tmp; scanf("%d", &tmp);
    		printf("%d\n", ans[tmp]);
    	}
    }
    

    树的中心

    概念

    以树的中心为整棵树的根时,从该根到每个叶子节点的最长路径最短


    求法

    d p [ u ] [ 0 ] dp[u][0] dp[u][0] 表示以 u u u 为根的子树中的最长链, d p [ u ] [ 1 ] dp[u][1] dp[u][1] 表示以 u u u 为根的子树中的次长链
    注意最长链和次长链没有交集, c [ u ] [ 0 ] c[u][0] c[u][0] / / / c [ u ] [ 1 ] c[u][1] c[u][1] 分别表示两者的决策点
    u p [ u ] up[u] up[u] 为从 u u u 这个点往上走的最远距离,注意这里往上指向上任意一个方向
    d p dp dp c c c 很容易求,求完之后考虑怎么求 u p up up

    对于一个点 u − > v u -> v u>v
    如果 v v v u u u 最长链的决策点, u p [ v ] = u p [ u ] up[v] = up[u] up[v]=up[u] u u u 的次长链取最大值 + w + w +w
    如果不是最长链决策点, u p [ v ] = u p [ u ] up[v] = up[u] up[v]=up[u] u u u 的最长链取最大值 + w + w +w


    模板题,求每个点为根时,到叶子结点的最长距离

    int n, dp[maxn][2], c[maxn][2], up[maxn];
    vector <pii> g[maxn];
    
    void dfs1(int u, int fa) {
    	for(auto vv : g[u]) {
    		int v = vv.first, w = vv.second;
    		if(v == fa) continue;
    		dfs1(v, u);
    		if(dp[v][0] + w > dp[u][0]) {
    			dp[u][1] = dp[u][0], c[u][1] = c[u][0];
    			dp[u][0] = dp[v][0] + w, c[u][0] = v;
    		} else if(dp[v][0] + w > dp[u][1])
    			dp[u][1] = dp[v][0] + w, c[u][1] = v;
    	}
    }
    
    void dfs2(int u, int fa) {
    	for(auto vv : g[u]) {
    		int v = vv.first, w = vv.second;
    		if(v == fa) continue;
    		if(v == c[u][0]) up[v] = max(up[u], dp[u][1]) + w;
    		else up[v] = max(up[u], dp[u][0]) + w;
    		dfs2(v, u);
    	}
    }
    
    signed main() {
    	while(~scanf("%d", &n)) {
    		memset(dp, 0, sizeof(dp));
    		memset(up, 0, sizeof(up));
    		for(int i=1; i<=n; i++) g[i].clear();
    		for(int i=2, v, w; i<=n; i++) {
    			scanf("%d%d", &v, &w);
    			g[i].push_back({v, w});
    			g[v].push_back({i, w});
    		}
    		dfs1(1, 0);
    		dfs2(1, 0);
    		for(int i=1; i<=n; i++) printf("%d\n", max(dp[i][0], up[i]));
    //		int pos, ans = 1e9;
    //		for(int i=1; i<=n; i++) 
    //			if(max(dp[i][0], up[i]) < ans) 
    //				ans = max(dp[i][0], up[i]), pos = i;
    //		printf("%d %d\n", pos, ans);
    	}
    }
    
    树上差分

    一、点差分

        对于树上路径 p a t h ( u , v ) path(u,v) path(uv)
         d l t [ u ] + + , d l t [ v ] + + , d l t [ l c a ( u , v ) ] − − , d l t [ f ( l c a ( u , v ) ) ] − − dlt[u]++, dlt[v]++, dlt[lca(u, v)] --, dlt[f(lca(u, v))] -- dlt[u]++dlt[v]++dlt[lca(u,v)]dlt[f(lca(u,v))]

        询问 x x x 被多少个标记覆盖 d f s dfs dfs
        从根节点开始,将其本身的权值加上所有子节点的权值
        每个节点的权值既是其被路径覆盖的次数


    二、边差分

        对于树上路径 p a t h ( u , v ) path(u,v) path(uv)
         d l t [ u ] + + , d l t [ v ] + + , d l t [ l c a ( u , v ) ] − = 2 dlt[u]++, dlt[v]++, dlt[lca(u, v)] -=2 dlt[u]++dlt[v]++dlt[lca(u,v)]=2

        询问 x x x 与其父亲的连边被多少个标记覆盖 d f s dfs dfs
        从根节点开始,将其本身的权值加上所有子节点的权值
        每个节点的权值即表示与其父亲的连边,被路径覆盖的次数


    树链剖分
    名称解释
    f [ u ] f[u] f[u]保存结点 u u u 的父亲节点
    d e p [ u ] dep[u] dep[u]保存结点 u u u 的深度值
    s i z e [ u ] size[u] size[u]保存以 u u u 为根的子树节点个数
    s o n [ u ] son[u] son[u]保存重儿子
    r k [ u ] rk[u] rk[u]保存当前 d f s dfs dfs 标号在树中所对应的节点
    t o p [ u ] top[u] top[u]保存当前节点所在链的顶端节点(替代 l c a lca lca
    i d [ u ] id[u] id[u]保存树中每个节点剖分以后的新编号( D F S DFS DFS的执行顺序)

    #define lson l,m,rt<<1
    #define rson m+1,r,rt<<1|1
    #define pushup(rt) t[rt] = t[rt<<1] + t[rt<<1|1];
    ll t[maxn<<2], lazy[maxn<<2], a[maxn];
    
    int n, m, f[maxn], size[maxn];
    int cnt, head[maxn], dep[maxn]; 
    int rk[maxn], rks, id[maxn];
    int son[maxn], top[maxn];
    
    struct EDGE {
    	int next, to, w;
    } edge[maxn<<2];
    
    void add(int u, int v, int w) {
    	edge[++cnt].next = head[u];
    	edge[cnt].to = v;
    	edge[cnt].w = w;
    	head[u] = cnt;
    }
    
    /*-------------------------树剖-------------------------*/
    void dfs1(int cur, int fa, int de){
    	dep[cur] = de, f[cur] = fa, size[cur] = 1;
    	for(int i=head[cur]; i; i=edge[i].next){
    		int v = edge[i].to;
    		if(v == fa) continue;
    		dfs1(v, cur, de+1);
    		size[cur] += size[v];
    		if(size[son[cur]] < size[v]) son[cur] = v;
    	}
    }
    
    void dfs2(int cur, int tp){
    	top[cur] = tp, id[cur] = ++rks, rk[rks] = cur;
    	if(son[cur]) dfs2(son[cur], tp);
    	for(int i=head[cur]; i; i=edge[i].next){
    		int v = edge[i].to;
    		if(v == f[cur]) continue;
    		if(v != son[cur]) dfs2(v, v);
    	}
    }
    /*-------------------------树剖-------------------------*/
    
    /*-------------------------线段树-------------------------*/
    void build(int l,int r,int rt){
    	lazy[rt] = 0;
    	if(l==r){
    		t[rt] = a[rk[l]];
    		return ;
    	}
    	int m = l + r >> 1;
    	build(lson); build(rson);
    	pushup(rt);
    }
    
    void pushdown(int l,int r,int rt) {
    	if(lazy[rt]) {
    		lazy[rt<<1] += lazy[rt];
    		lazy[rt<<1|1] += lazy[rt];
    		t[rt<<1] += l*lazy[rt];
    		t[rt<<1|1] += r*lazy[rt];
    		lazy[rt] = 0;
    	}
    }
    
    void update(int L,int R,int C,int l,int r,int rt) {
    	if(L<=l&&r<=R) {
    		t[rt] += (r-l+1)*C;
    		lazy[rt] += C;
    		return ;
    	}
    	int m = (l+r)>>1;
    	pushdown(m-l+1,r-m,rt);
    	if(L<=m) update(L,R,C,lson);
    	if(R>m) update(L,R,C,rson);
    	pushup(rt);
    }
    
    ll query(int L,int R,int l,int r,int rt) {
    	if(L<=l&&r<=R) return t[rt];
    	int m = (l+r)>>1;
    	pushdown(m-l+1,r-m,rt);
    	ll ans = 0;
    	if(L<=m) ans += query(L,R,lson);
    	if(R>m) ans += query(L,R,rson);
    	return ans;
    }
    /*-------------------------线段树-------------------------*/
    
    /*----------------------树剖 + 线段树----------------------*/
    ll sum(int x, int y){
    	ll ret = 0;
    	while(top[x] ^ top[y]){
    		if(dep[top[x]]<dep[top[y]]) swap(x, y);
    		ret += query(id[top[x]],id[x],1,n,1);
    		x = f[top[x]];
    	}
    	if(id[x]>id[y]) swap(x, y);
    	return ret + query(id[x],id[y],1,n,1);
    }
    
    void updates(int x, int y, int c){
    	while(top[x] ^ top[y]){
    		if(dep[top[x]]<dep[top[y]]) swap(x, y);
    		update(id[top[x]],id[x],c,1,n,1);
    		x = f[top[x]];
    	}
    	if(id[x]>id[y]) swap(x, y);
    	update(id[x],id[y],c,1,n,1);
    }
    /*----------------------树剖 + 线段树----------------------*/
    
    int main() {
    	int rt;
    	scanf("%d%d%d%d", &n, &m, &rt, &mod);
    	for(int i=1; i<=n; i++) scanf("%d", a+i);
    	for(int i=1; i<n; i++){
    		int u, v; scanf("%d%d", &u, &v);
    		add(u, v, 1); add(v, u, 1);
    	}
    	dfs1(rt, 0, 1); dfs2(rt, rt); build(1, n, 1);
    	for(int i=1; i<=m; i++){
    		int x, y, c, op; scanf("%d", &op);
    		if(op == 1){
    			scanf("%d%d%d", &x, &y, &c);
    			updates(x, y, c);
    		}
    		else if(op == 2){
    			scanf("%d%d", &x, &y);
    			printf("%lld\n", sum(x, y));
    		}		
    		else if(op == 3){
    			scanf("%d%d", &x, &c);
    			update(id[x],id[x]+size[x]-1,c,1,n,1);
    		}
    		else {
    			scanf("%d", &x);
    			printf("%lld\n", query(id[x],id[x]+size[x]-1,1,n,1));
    		}
    	}
    }
    

    —— 最小生成树 ——

    Prim + 邻接链表 + 堆优化(优先队列):

    int n, m, cnt, ans, vis[maxn], head[maxn], dis[maxn]; 
    priority_queue <pii, vector<pii>, greater<pii> > q;
    struct EDGE{
    	int next, to, w;  
    } e[maxn<<1]; 
    
    void add(int u, int v, int w) {  
    	e[++cnt].next = head[u];
    	e[cnt].w = w;
    	e[cnt].to = v;
    	head[u] = cnt;    
    }
    
    void prim(int st){
    	memset(vis,0,sizeof(vis));
    	for(int i=1; i<=n; i++) dis[i] = INT_MAX;
    	q.push(make_pair(0, st));
    	dis[st] = 0; int num = 0;
    	while(!q.empty() && num<n){
    		int d = q.top().first;
    		int k = q.top().second;
    		q.pop();
    		if(vis[k]) continue;
    		num++; ans += d; vis[k] = 1;
    		for(int i=head[k]; i; i=e[i].next)
    			if(e[i].w<dis[e[i].to]){
    				dis[e[i].to] = e[i].w;
    				q.push(make_pair(e[i].w, e[i].to));
    			}
    	}
    }
    
    int main() {
    	n = read(), m = read();
    	memset(head,0,sizeof(head));
    	ans = cnt = 0;
    	for(int i=0; i<m; i++){
    		int u, v, w;
    		u = read(), v = read(), w = read();
    		add(u, v, w); add(v, u, w);
    	}
    	prim(1); printf("%d\n", ans);
    }
    

    Kruskal:

    int n, m, cnt, ans, f[maxn]; 
    struct EDGE{
    	int u, v, w;
    	bool operator <(const EDGE &A) const { return w<A.w; }
    } e[maxn];
    
    int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
    int main() {
    	int u, v, w;
    	n = read(), m = read();
    	for(int i=1; i<=n; i++) f[i] = i;
    	for(int i=0; i<m; i++){
    		e[i].u = read(); e[i].v = read(); e[i].w = read();
    	}
    	sort(e, e+m);
    	for(int i=0; i<m; i++){
    		u = find(e[i].u); v = find(e[i].v);
    		if(u!=v){
    			f[u] = v; ans += e[i].w; cnt++;
    		}
    		if(cnt==n-1) break;
    	}
    	printf("%d\n", ans);
    }
    

    —— 欧拉回路 ——

    判定

    有向图回路:图联通 & & \&\& && 每个点入度 = = = 出度
    无向图回路:图联通 & & \&\& && 每个点的度数为偶数

    有向图通路:图联通 & & \&\& && 每个点入度 = = = 出度
          或者可以存在两个点,其中一个出度比入度大 1 1 1,为路径的起点
          另外一个入度比出度大 1 1 1,为路径的终点
    无向图通路:图联通 & & \&\& && 度数为奇数的的点只有 2 2 2 个或者 0 0 0
          当有两个奇点时,一个为起点,另一个为终点


    欧拉回路 输出

    从起点开始找一条回路
    找出有尚未访问的边的路径上的第一个顶点, 并执行另外一次深度优先搜索
    这将给出另外一个回路, 把它拼接到原来的回路上
    继续该过程直到所有的边都被遍历为止

    s o l v e 1 solve1 solve1 为无向图回路, s o l v e 2 solve2 solve2 为有向图回路

    int T, n, m;
    
    bool solve1() {		//	无向图 
    	scanf("%d%d", &n, &m);
    	vector <vector <pii> > g(2 * n + 5);
    	for(int i=1, u, v; i<=m; i++) {
    		scanf("%d%d", &u, &v);
    		g[u].push_back({v, i});
    		g[v].push_back({u, -i});
    	}
    	for(int i=1; i<=n; i++)
    		if(g[i].size() & 1) return printf("NO\n"), 0;
    	vector <bool> del(m + 5);
    	vector <int> ans;
    	function <void(int, int)> dfs = [&] (int u, int fa) {
    		while(g[u].size()) {
    			auto it = g[u].back(); g[u].pop_back();
    			int v = it.first, id = it.second;
    			if(!del[abs(id)]) {
    				del[abs(id)] = true;
    				dfs(v, id);
    			}
    		}
    		if(fa != 0) ans.push_back(fa);
    	};
    	for(int i=1; i<=n; i++)
    		if(g[i].size()) {
    			dfs(i, 0);
    			break;
    		}
    	if(ans.size() != m) return printf("NO\n"), 0;
    	printf("YES\n");
    	for(int i=ans.size()-1; ~i; i--)
    		printf("%d ", ans[i]);
    }
    
    bool solve2() {		//	有向图 
    	scanf("%d%d", &n, &m);
    	vector <int> in(n + 5), out(n + 5);
    	vector <vector <pii> > g(2 * n + 5);
    	for(int i=1, u, v; i<=m; i++) {
    		scanf("%d%d", &u, &v);
    		g[u].push_back({v, i});
    		in[v]++, out[u]++;
    	}
    	for(int i=1; i<=n; i++)
    		if(in[i] ^ out[i]) return printf("NO\n"), 0;
    	vector <bool> del(m + 5);
    	vector <int> ans;
    	function <void(int, int)> dfs = [&] (int u, int fa) {
    		while(g[u].size()) {
    			auto it = g[u].back(); g[u].pop_back();
    			int v = it.first, id = it.second;
    			if(!del[id]) {
    				del[id] = true;
    				dfs(v, id);
    			};
    		}
    		if(fa != 0) ans.push_back(fa);
    	};
    	for(int i=1; i<=n; i++)
    		if(g[i].size()) {
    			dfs(i, 0);
    			break;
    		}
    	if(ans.size() != m) return printf("NO\n"), 0;
    	printf("YES\n");
    	for(int i=ans.size()-1; ~i; i--)
    		printf("%d ", ans[i]);
    }
    
    signed main() {
    	scanf("%d", &T);
    	T == 1 ? solve1() : solve2();
    }
    

    欧拉通路 输出

    int T, n, m;
    
    signed main() {
    	scanf("%d%d", &n, &m);
    	vector <vector <pii> > g(2 * n + 5);
    	for(int i=1, u, v; i<=m; i++) {
    		scanf("%d%d", &u, &v);
    		g[u].push_back({v, i});
    		g[v].push_back({u, -i});
    	}
    	int st = 1; 
    	for(int i=1; i<=n; i++)
    		if(g[i].size() & 1) st = i;
    	vector <bool> del(m + 5);
    	vector <int> ans;
    	function <void(int, int)> dfs = [&] (int u, int fa) {
    		while(g[u].size()) {
    			auto it = g[u].back(); g[u].pop_back();
    			int v = it.first, id = it.second;
    			if(!del[abs(id)]) {
    				del[abs(id)] = true;
    				dfs(v, id);
    			}
    		}
    		ans.push_back(u);
    	};
    	for(int i=1; i<=n; i++)
    		if(g[i].size()) {
    			dfs(st, 0);
    			break;
    		}
    	for(int i=ans.size()-1; ~i; i--)
    		printf("%d ", ans[i]);
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值