相关目录:
图论
Dijkstra
时间复杂度: m l o g n mlogn mlogn
边权:非负
性质:按照dijkstra更新的点时满足拓扑序的。
dijkstra求次短路
int dijkstra() { mem(dist, 0x3f); mem(st, false); mem(cnt, 0); dist[s][0] = 0; cnt[s][0] = 1; priority_queue<Node, vector<Node>, greater<Node>> heap; heap.push({0, 0, s}); while(heap.size()) { auto t = heap.top(); heap.pop(); int d = t.dist, type = t.type, id = t.id; if(st[id][type]) continue; st[id][type] = true; for(int i = h[id]; ~i; i = ne[i]) { int j = e[i]; if(dist[j][0] > d + w[i]) { dist[j][1] = dist[j][0], cnt[j][1] = cnt[j][0];//最短被更新,次短继承最短。 heap.push({dist[j][1], 1, j});//将更新的次短路入堆 dist[j][0] = d + w[i], cnt[j][0] = cnt[id][type]; //更新最短路 heap.push({dist[j][0], 0, j});//将更新的最短路入堆 } else if(dist[j][0] == d + w[i]) cnt[j][0] += cnt[id][type];//找到等价最短路 else if(dist[j][1] > d + w[i])//次短路被更新 { dist[j][1] = d + w[i]; cnt[j][1] = cnt[id][type]; heap.push({dist[j][1], 1, j});//将更新的次短路入堆 } else if(dist[j][1] == d + w[i]) cnt[j][1] += cnt[id][type];//找到等价次短路 } } int ans = cnt[t][0]; if(dist[t][0] + 1 == dist[t][1]) ans += cnt[t][1]; return ans; }
Spfa
时间复杂度:
边权:可正可负
边权非负还是用dijkstra保险。
Floyd
时间复杂度: O ( n 3 ) O(n^3) O(n3)
思想:DP
d[k][i][j]: i i i到 j j j只经过 1 − k 1 - k 1−k的最短路
插点更新:用k点更新之前,最短路是由1~k-1来更新的。
板子:
mem(d, 0x3f, sizeof d); rep(i, 1, n) d[i][i] = 0; rep(k, 1, n) rep(i, 1, n) rep(j, 1, n) if(d[i][j] > d[i][k] + d[k][j]) { d[i][j] = d[i][k] + d[k][j]; path[i][j] = k;//记录路径 }
Floyd 找最小环?
DP思想:
以K(环中最大点)枚举所有的环。
/*
author: A Fei
*/
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mem(x, a) memset(x, a, sizeof x)
#define pb(x) push_back(x)
#define rep(i, l, r) for(int i = l; i <= (r); ++ i)
#define per(i, r, l) for(int i = r; i >= (l); -- i)
using namespace std;
typedef long long LL;
typedef double DB;
typedef pair<int, int> PII;
typedef pair<int, double> PID;
typedef pair<double, double> PDD;
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
const int INF = 0x3f3f3f3f;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const int N = 110;
int g[N][N], d[N][N];
int path[N][N];//保存路径。
int n, m;
vector<int> ans;
void dfs(int i, int j)//输出最短路部分的路径
{
int k = path[i][j];
if(!k) return ;
dfs(i, k);
ans.pb(k);
dfs(k, j);
}
void get_path(int i, int j, int k)//记录最小环的方案:i -> 最短路部分 -> j -> k
{
ans.clear();
ans.pb(i);
dfs(i, j);
ans.pb(j);
ans.pb(k);
}
int main()
{
cin >> n >> m;
memset(g, 0x3f, sizeof g);
rep(i, 1, n) g[i][i] = 0;
while (m -- )
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
memcpy(d, g, sizeof g);//将g备份到d,用d跑最短路。
int res = INF;
rep(k, 1, n)
{
//d此时为:只被1~k-1更新的最短路,来保证k为环中最大的点。
rep(i, 1, k - 1)//i,j,k不相同来,找以K最大点的最小环。
rep(j, i + 1, k - 1)//i < j < k
if(res > (LL)g[i][k] + g[k][j] + d[i][j])//三个都是INF的话,这里可能会爆。。。。艹
{
res = g[i][k] + g[k][j] + d[i][j];//更新最小环的权值。
get_path(i, j, k);//记录目前最小环的路径。
}
//Floyd更新最短路并记录路径
rep(i, 1, n)
rep(j, 1, n)
if(d[i][j] > d[i][k] + d[k][j])
{
d[i][j] = d[i][k] + d[k][j];
path[i][j] = k;
}
}
if(res == INF) puts("No solution.");
else for(auto x : ans) cout << x << " ";
return 0;
}
类Floyd(坑)
Floyd的倍增算法?
DP – 状态表示:d[k][i][j]: i i i到 j j j恰好经过k条边的最短路.
最小生成树
**1.**将所有边加入结构体,并从小到大排序。
**2.**选择两端点不在一个集合的边, 并将两端点合并.
(建图的时候可能会有离散化的操作)
次小生成树(坑)
定义给定一个带权的图,把图的所有生成树按权值从小到大排序,第二小的称为次小生成树。
步骤:
1.先构建最小生成树。
2.枚举非树边,将非树边替换至最小生成树中,同时从树中去掉一条边,使得最终的图仍是一颗树。
eg:秘密的奶牛运输
解法:dfs + 最小生成树 + 枚举非树边
负(正)环
判法一:
一个点最多被 n − 1 n-1 n−1的点相连;当某一个点入队次数 > = >= >=n即被更新了多于n次。即存在负(正)环。
判法二:
n n n个点的图,最短路只可能存在 n − 1 n-1 n−1条边,当某个点被更新时,从起点到该点的边数 > = n >=n >=n时,即存在负(正)环。一般用第二种判法
因为当 n n n个点形成一个大负(正)环,用判法一则需要转 n n n圈才能结束– O ( n 2 ) O(n^2) O(n2);判法二则需要转一圈即可 – O ( n ) O(n) O(n)。
玄学优化:
1.使用栈会比队列效率高。
2.经验值trick-- 当所有点入队次数超过2N,我们就认为图中很大可能存在负环。
Code:
/*
author: A Fei
solution:
将每个点的前两个字母和后两个字母看成两个点!
设ans为解 --> ans * (n) - (w[1]+w[2]+w[3]+...+w[n]) = 0
二分mid,当mid < ans 则存在负环;当mid >= ans不存在负环。
即存在二分性质。
*/
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mem(x, a) memset(x, a, sizeof x)
#define pb(x) push_back(x)
#define rep(i, l, r) for(int i = l; i <= (r); ++ i)
#define per(i, r, l) for(int i = r; i >= (l); -- i)
using namespace std;
typedef long long LL;
typedef double DB;
typedef pair<int, int> PII;
typedef pair<int, double> PID;
typedef pair<double, double> PDD;
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
const int INF = 0x3f3f3f3f;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const int N = 700, M = 9e5 + 10;
int h[N], ne[M], e[M], w[M], idx;
double dist[N], cnt[N];
bool st[N];
int n, Cnt;
void add(int a, int b, int c)
{
ne[idx] = h[a], e[idx] = b, w[idx] = c, h[a] = idx ++;
}
bool check(double mid)
{
// mem(st, false);
stack<int> q;
rep(i, 1, Cnt) q.push(i), st[i] = true, dist[i] = 0, cnt[i] = 0;
int sum = 0;
while(q.size())
{
int t = q.top();
q.pop();
st[t] = false;
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + mid - w[i])
{
dist[j] = dist[t] + mid - w[i];
cnt[j] = cnt[t] + 1;
if(cnt[j] >= N) return true;
if(++ sum >= 10000) return true;
if(!st[j]) q.push(j), st[j] = true;
}
}
}
return false;
}
int main()
{
while(scanf("%d", &n), n)
{
mem(h, -1);
idx = 0;
unordered_map<string, int> id;
Cnt = 0;
rep(i, 1, n)
{
string s;
cin >> s;
if(s.length() <= 1) continue;
string ss = s.substr(0, 2);
if(!id[ss]) id[ss] = ++ Cnt;
string ee = s.substr(s.length() - 2);
if(!id[ee]) id[ee] = ++ Cnt;
add(id[ss], id[ee], s.length());
// cout << ss << " " << ee << " " << s.length() << endl;
}
if(!check(0))
{
puts("No solution");
continue;
}
double l = 0, r = 10000, eps = 1e-3;
while(r - l >= eps)
{
double mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.2lf\n", l);
}
return 0;
}
差分约束
利用图来解决:求不等式组的可行解和最大最小值。
求不等式组可行解:
步骤:
1.将不等式组变形为形如这样的: X i < = X j + C k X_i <= X_j + C_k Xi<=Xj+Ck
连接一条 X j X_j Xj -> X i X_i Xi权值为 C k C_k Ck的边.(即:将限制条件变为一条边)
2.建立炒鸡源点能讲遍历到所有边(所有限制条件).
3.在建立的图上面跑最短路或最长路即可得到一组可行解.
求最大最小值:
结论:
求最大值跑最短路;求最小值跑最长路.解释:
有一条边(限制条件): X j X_j Xj -> X i X_i Xi,边权为 C k C_k Ck.当跑最短路后:
d [ i ] < = d [ j ] + C k d[i] <= d[j] + C_k d[i]<=d[j]+Ck当跑最长路后:
d [ i ] > = d [ j ] + C k d[i] >= d[j] + C_k d[i]>=d[j]+Ck跑完最短路后:我们求出的是每个点 i i i的上限,将求得的每个点的上限相加即得到最大值.
跑完最长路后:我们求出的是每个点 i i i的下限,将求得的没给单的下限相加即得到最小值.注:边与边的关系求出的只是每个变量之间的相对关系.要想求出最大最小值,我们需要一个形如: X y > = C X_y >= C Xy>=C(绝对关系), 并通过这个变量能得到其他变量的的绝对关系.
eg: 区间
解法一:差分约束
解法二:贪心 + 线段树优化(挖坑)
Code:
/*
author: A Fei
solution:
艹,自己搞得前缀和,条件搞了一坨屎,还搞了个牛马离散。-- https://www.acwing.com/problem/content/submission/code_detail/13406556/
前缀和:
条件一:s_i >= s_i-1 add(si-1, si, 0)
条件二:s_i - s_i-1 <= 1 --> s_i-1 >= s_i - 1 add(si, si-1, -1)
条件三:s_b - s_a-1 >= c add(sa-1, sb, c);
求完最短路,s_50001即为答案。
*/
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mem(x, a) memset(x, a, sizeof x)
#define pb(x) push_back(x)
#define rep(i, l, r) for(int i = l; i <= (r); ++ i)
#define per(i, r, l) for(int i = r; i >= (l); -- i)
using namespace std;
typedef long long LL;
typedef double DB;
typedef pair<int, int> PII;
typedef pair<int, double> PID;
typedef pair<double, double> PDD;
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
const int INF = 0x3f3f3f3f;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const int N = 5e4 + 10, M = 150001;
int h[N], ne[M], e[M], w[M], idx;
int dist[N];
bool st[N];
int n, m;
void add(int a, int b, int c)
{
ne[idx] = h[a], e[idx] = b, w[idx] = c, h[a] = idx ++;
}
void spfa()
{
mem(dist, -0x3f);
queue<int> q;
q.push(0), st[0] = true, dist[0] = 0;
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if(dist[j] < dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j]) q.push(j), st[j] = true;
}
}
}
}
int main()
{
scanf("%d", &n);
mem(h, -1);
rep(i, 1, N - 1) add(i - 1, i, 0), add(i, i - 1, -1);
rep(i, 1, n)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
a ++, b ++;
add(a - 1, b, c);
}
// exit(0);
spfa();
cout << dist[50001];
return 0;
}
最近公共祖先(LCA - Least Common Ancestors)
法一: 倍增
f [ i , j ] f[i,j] f[i,j]:表示从i开始,向上走 2 j 2^j 2j步所走到的节点。
depth[i]:表示i点的深度。
步骤:
- 先将两个点跳到同一层。
2.让两个点同往上跳,一直跳到它们最近公共祖先的下一层子节点。
- O ( n l o g n + m l o g n ) O(nlogn + mlogn) O(nlogn+mlogn):预处理 O ( n l o g n ) O(nlogn) O(nlogn) + 查询 O ( m l o g n ) O(mlogn) O(mlogn)
eg:祖孙询问
LCA:可以求两个点之间的最近祖先;两个点之间距离的最大,次大,最小值。
Code:
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void bfs(int r)
{
memset(depth, 0x3f, sizeof depth);
queue<int> q;
q.push(r);
depth[r] = 1;//根节点的深度定义为1
depth[0] = 0;//哨兵深度定义为0,设立哨兵方便处理跳过根节点的情况
while(q.size())
{
int t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
//表示未遍历过
if(depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q.push(j);
fa[j][0] = t;//此时j的父亲即为t
for(int k = 1; k <= 16; k ++)//更新j的祖宗们
fa[j][k] = fa[fa[j][k-1]][k-1];
/*
fa[j][k]:j向上2^k步的祖宗
fa[j][k-1]:j向上2^k-1步的祖宗,在该基础上再走2^k-1步即为fa[fa[j][k-1]][k-1]
所以:fa[j][k] = fa[fa[j][k-1]][k-1];
如果向上已经超过根节点,那么祖宗节点即为哨兵0
*/
}
}
}
}
int lca(int a, int b)
{
if(depth[a] < depth[b]) swap(a, b);
//先将a, b跳到同一层
for(int i = 16; i >= 0; i --)
if(depth[fa[a][i]] >= depth[b]) a = fa[a][i];
/*
如果跳过的话,那么有depth[fa[a][i]] = 0 < depth[b] 就不会跳这么远,就会向近一点的考虑
*/
if(a == b) return a;
/*
此时两个点同时向上跳
1.如果跳过根节点,那么fa[a][i] = fa[b][i] = 0,不会跳过去
2.if(fa[a][i] != fa[b][i])这个限制条件保证了跳的位置一定是在最近公共点的下方,最后一定会停在最近公共点的子节点处
*/
for(int i = 16; i >= 0; i --)
if(fa[a][i] != fa[b][i]) a = fa[a][i], b = fa[b][i];
return fa[a][0];
}
法二: Tarjan 离线LCA
时间复杂度: O ( m + n ) O(m+n) O(m+n)
个人赶脚很鸡肋,没倍增的好用,等倍增被卡了,再学。
树上差分
eg: 闇の連鎖
有向图的强连通分量
概念:
对于一个有向图来说:
连通分量:对于分量中任意两点, u , v u, v u,v,必然可以从 u u u走到 v v v,且从 u u u走到 v v v。
强连通分量(SCC-strongly connected component):极大连通分量 – 加上任意一个点都不是连通分量。
关于有向图的知识:
黑色:树枝边
蓝色:前向边
绿色:后向边
红色:横插边当存在后向边或横插边,才可能存在SCC
Tarjan求SCC:
对每个点定义两个时间戳 – dfn[u], low[u]
u是所在强连通分量的最够点,等价与 dfn[u] == low[u]
具体Code:
const int N = 1e4 + 10, M = 5e4 + 10;
int h[N], ne[M], e[M], idx;//邻接表
//时间戳
//dfn[u]:表示遍历到u的时间戳
//low[u]:从u开始走,所能遍历到的最小时间戳
int dfn[N], low[N], timesamp;
int stk[N], top;//栈
int scc_cnt, scc_size[N], scc_id[N];//强联通块数量;每个强联通块中点的数量;强联通块的编号
bool in_stk[N];//元素是否入栈
int dout[N];//每个强联通块的出度
int n, m;
void add(int a, int b)
{
ne[idx] = h[a], e[idx] = b, h[a] = idx ++;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ timesamp;//给u赋予时间戳
stk[++ top] = u, in_stk[u] = true;//将u入栈
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(!dfn[j])//j没遍历过
{
tarjan(j);//遍历j
low[u] = min(low[j], low[u]);//回溯,更新u所能到的最小时间戳
}
else if(in_stk[j])//儿子已经在栈里面,即u这条边是一条后向边或横插边 --> 意味着j的时间戳是小于u的时间戳
low[u] = min(low[u], dfn[j]);
}
//不懂这点就模拟一下:1->2, 2->3, 3->1这个图
if(dfn[u] == low[u])//回溯的时候,发现u的时间戳和所能到的最小时间戳相等,那么从栈顶到栈中的u之间的元素就是一个SCC
{
++ scc_cnt;//SCC数量+1
int y;
do{
y = stk[top --];//取出栈中元素
in_stk[y] = false;//出栈置为false
scc_id[y] = scc_cnt;//给元素y编号
scc_size[scc_cnt] ++;//该SCC点数量+1
}while(y != u);
}
}
无向图的双连通分量(坑)
二分图
二分图指无向图。
1.二分图 <=> 图中不存在奇数(边)环 <=> 染色法不存在矛盾
2.匈牙利算法(求二分图最大匹配),匹配,最大匹配,匹配点,增广路径
3.最小点覆盖,最大独立集,最小路径点覆盖(最小路径重复点覆盖)无向图中: 最 大 匹 配 数 = 最 小 点 覆 盖 = 总 点 数 ( 两 边 总 点 数 ) − 最 大 独 立 集 最大匹配数 = 最小点覆盖 = 总点数 (两边总点数)- 最大独立集 最大匹配数=最小点覆盖=总点数(两边总点数)−最大独立集
有向无环图: 最 大 匹 配 数 = 总 点 数 ( 一 边 总 点 数 ) − 最 小 路 径 点 覆 盖 最大匹配数 = 总点数(一边总点数)- 最小路径点覆盖 最大匹配数=总点数(一边总点数)−最小路径点覆盖最小点覆盖: 给定一张无向图,每条边至少选一个点,所选点数 最少。
最大独立集:给定一张无向图,选出最多的点,使得选出的点之间没有边(等价:选最少的点,将边破坏掉 即:总点数 - 最小点覆盖)。
最大团:给定一张无向图,选出最多的点,使得任意两点都有边。
- 最大独立集与最大团:互补。
- 图 G G G最大独立集 = 补图 G 1 G_1 G1的最大团。
图 G G G去掉已有的边,加上没有的边 --> 补图 G 1 G_1 G1。最小路径点覆盖:有向无环图中,用最少的互不相交(点不重复)的路径,将所有点覆盖。
匈牙利算法(二分图最大匹配):
只建立有向边(从一边到另一边)即可。
Code(二分图最大匹配):
/*
author: A Fei
solution:
不考虑不能放的格子,将坐标和为偶数的记为白格子,奇数的记为黑格子。
显然白色格子周围都是黑色格子,黑色格子周围都是白色格子,当白色格子向走位黑色格子连一条有向边时即骨牌数量+1
同色之间不可能相连,故该图为二分图。
问题转化为求二分图的最大匹配。
*/
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mem(x, a) memset(x, a, sizeof x)
#define pb(x) push_back(x)
#define rep(i, l, r) for(int i = l; i <= (r); ++ i)
#define per(i, r, l) for(int i = r; i >= (l); -- i)
using namespace std;
typedef long long LL;
typedef double DB;
typedef pair<int, int> PII;
typedef pair<int, double> PID;
typedef pair<double, double> PDD;
LL read(){LL x=0,f=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}while(c>='0'&&c<='9')x=x*10+(c-'0'),c=getchar();return f*x;}
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
const int INF = 0x3f3f3f3f;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const int N = 110;
PII match[N][N];
int n, m;
bool bad[N][N], st[N][N];
bool find(int x, int y)
{
rep(i, 0, 3)
{
int a = x + dx[i], b = y + dy[i];//邻近格子
if(a < 1 || a > n || b < 1 || b > n || bad[a][b] || st[a][b]) continue;//出界or格子不能放or格子遍历过
st[a][b] = true;//妹子被遍历
PII t = match[a][b];//t --> 妹子的对象
if(t.fi == -1 || find(t.fi, t.se))//-1:妹子没对象 or 妹子的对象可以换个对象 则匹配成功
{
match[a][b] = {x, y};//将妹子的对象换为x,y
return true;//x,y与a,b配对成功
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
rep(i, 1, m)
{
int a, b;
scanf("%d%d", &a, &b);
bad[a][b] = true;
}
mem(match, -1);
int res = 0;
//枚举一边:白色格子or黑色格子
rep(i, 1, n)
rep(j, 1, n)
{
mem(st, false);
if(((i + j) & 1) && !bad[i][j] && find(i, j))//枚举奇数格子,能放且能分配白格子
res ++;
}
printf("%d\n", res);
return 0;
}
欧拉回路和欧拉路径
- 所有边联通的无向图:
(1)存在欧拉路径的充要条件:度数位奇数的点只能有0或2个。
(2)存在欧拉回路的充要条件:度数位奇数的点只能有0个。2.所有边都联通的有向图:
(1)。存在欧拉路径的充要条件:
a.所有点出度均等于入度
或
b.除起点(入度+1=出度)和终点(入度=出度+1)外,剩余点入度=出度。(2).存在欧拉回路的充要条件:所有点的出度=入度。
步骤:
1.通过有向图/无向图的入度出度条件判断是否有欧拉路径(回路)
2.建图,遍历图
3.每遍历一条边就将其删除
4.在回溯时记录路径,将记录的路径逆序输出即为真正路径。
Code:骑马修栅栏–邻接矩阵删边
const int N = 510, M = 2100;
int g[N][N];
int n, m, cnt;
int d[N], path[N];
void dfs(int u)
{
rep(i, 1, 500)
if(g[u][i])
{
g[u][i] --, g[i][u] --;
dfs(i);
}
path[++ cnt] = u;
}
int main()
{
scanf("%d", &m);
rep(i, 1, m)
{
int a, b;
scanf("%d%d", &a, &b);
g[a][b] ++, g[b][a] ++;
d[a] ++, d[b] ++;
}
int start = 1;
while(!d[start]) start ++;
rep(i, start, 500)//要从一个奇度点出发
if(d[i] & 1)
{
start = i;
break;
}
dfs(start);
per(i, cnt, 1) printf("%d\n", path[i]);
return 0;
}
Code:欧拉回路–邻接表删边
/*
author: A Fei
solution:
1.当存在自环时O(m^2)的原因:
大概是这么个意思,只有一个点的情况下,
假如第一次把第一个环标记了,在遍历第二个环的时候又回到了第一个点(因为只有一个点,所以就围着点转圈),
那么还是会枚举第一条边,虽然会接着被continue掉,
以此类推,在枚举到第m条边的时候,还是会枚举到前边m-1条边(虽然会被continue掉),
那么这就成了O(m2)O(m2)了,但是如果直接删掉的话就直接少了枚举第一条边了,那么复杂度就成了线性了。
-- tocsu的评论:https://www.acwing.com/video/592/
2.修改前TLE以及y总加完引用AC的原因:
a。递归到最后一层时,h[u]=-1了,再回溯时whie(~h[u])就结束了。
原来y总代码,虽然h[u]最后也等于-1,但是结束条件是~i, i=ne[i]还是继续遍历
b。详细解释 - https://www.acwing.com/solution/content/38082/
3.倒序记录边的原因:
一个点的出边可能有多条,有些出边可能要回溯的时候才能访问到,如果遍历到的时候直接正序记录,
那么终点/终边可能就会出现在回溯后才能访问到的点/边前面,这样记录的顺序就是错的。
-- jzdx的评论:https://www.acwing.com/video/592/
tricks:
a.无向图:i的反向边:h[i^1]
b.无向图:0,1为第一条边;2,3为第二条边;4,5为第三条边...
*/
#include <bits/stdc++.h>
const int N = 1e5 + 10, M = 4e5 + 10;
int h[N], ne[M], e[M], idx;
bool used[M];
int cnt, path[M / 2];
int din[N], dout[N];
int type;
int n, m;
void add(int a, int b)
{
ne[idx] = h[a], e[idx] = b, h[a] = idx ++;
}
void dfs(int u)
{
while(h[u] != -1)
{
int i = h[u];
if(used[i])//该边被用过
{
h[u] = ne[i];
continue;
}
h[u] = ne[i];//这条边用过删除
if(type == 1) used[i ^ 1] = true;//该边的反向也标记
dfs(e[i]);
int t = i;
if(type == 1) //0, 1为第一条边;2,3为第二条边;...
{
t = t / 2 + 1;
if(i & 1) t = -t;
}
else t ++;
path[++ cnt] = t;
}
}
int main()
{
mem(h, -1);
scanf("%d", &type);
scanf("%d%d", &n, &m);
rep(i, 1, m)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
if(type == 1) add(b, a);
din[b] ++, dout[a] ++;
}
if(type == 1)//无向图
{
rep(i, 1, n)
if((din[i] + dout[i]) & 1)//存在奇度点
{
puts("NO");
return 0;
}
}
else //有向图
{
rep(i, 1, n)
if(din[i] != dout[i])//入度与出度不同
{
puts("NO");
return 0;
}
}
//找到有边的点爆搜
rep(i, 1, n)
if(h[i] != -1)
{
dfs(i);
break;
}
//没搜到全部的边--边不连通
if(cnt != m)
{
puts("NO");
return 0;
}
puts("YES");
// cout << cnt;
per(i, cnt, 1)
printf("%d ", path[i]);
return 0;
}
拓扑排序
a. DAG存在拓扑序
b. 存在拓扑序之后就可以DP,记忆化搜索,用Bfs跑最长最短路…
步骤:
a.统计每个点的入度
b.将入度为0的入队
c.跑图,遍历一条边就将对点入度- -,若为0,将其入队。
eg:车站分级
Code:
void topsort()
{
rep(i, 1, n) dist[i] = 1;
queue<int> heap;
rep(i, 1, n + m)
if(!din[i]) heap.push(i);//车站编号至少为1
while(heap.size())
{
int t = heap.front();
heap.pop();
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
dist[j] = max(dist[j], dist[t] + w[i]);
if(-- din[j] == 0) heap.push(j);
}
}
}