恩對我們要來講圖論了
(bruh TOI 初選炸掉)菜
歐對 我打字很快算學術力嗎 :U(NO)
圖是什麼 可以吃嗎?
為離散數學的一部分 (恩我們要開始講數學了)
圖用來表示有限集合中物件之間的關係,物件在圖中以「節點/頂點(node / vertex)」呈現、關係以「邊(edge / arc)」呈現。
* 無向圖也可以稱為簡單圖
(可以想像成一個城市的捷運和另外一個城市的捷運,分成兩塊)
有向圖時,可以分成in度數和out度數
點C的度數為2
點C的in度數為2,out度數為1
最直接的作法就是存一個二維陣列,可以互相走到就設為1 (ex: a[1][2] = 1,1可以走到2)
這也叫做鄰接陣矩 (adjacency matrix)
但是鄰接矩陣會發現一個問題,就是在很稀疏的圖它會浪費很多空間,並且會需到 O(n^2) 的空間,空間幾乎是白開的
bool f[maxn][maxn];
for (int i=1; i<=n; ++i){
for (int j=1; j<=n; ++j) cin >> f[i][j];
}作法就是每個點開一個vector,並且放入那個點可以走到的點
這也叫做鄰接陣列 (adjacency list)
(m 為圖中的邊數)
vector<int> e[maxn];
for (int i=1; i<=m; ++i){
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}u, v 代表兩點之間有無向邊
那我們就兩點都要存
(相反的 有向邊就只要存開頭的那個點)
廣度優先搜尋
假設我們有一張無權圖,要從A點開始,遍歷整張圖
我們可以一直慢慢擴散出去,不斷尋找下點可以去哪
注意:走過的就不用在走了
最後我們就成功用BFS遍歷整張圖了
時間複雜度為 O(n+m)
並且會發現一個特性:
因為我們是一層一層的作,所以我們抵達一個點時他一定是最短距離,所以BFS可以用來求無權圖點到其他點的最短距離
給點與點之間的有向邊
求可不可以從一點走到一點
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 805
using namespace std;
int n, m, s, t;
vector<int> e[maxn];
bool vis[maxn];
bool f;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
while (cin >> n >> m){
//__init__
f = false; queue<int> q;
for (int i=1; i<=n; ++i){
vis[i] = 0;
e[i].clear();
}
for (int i=1; i<=m; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
}
cin >> s >> t;
q.push(s);
while (!q.empty()){
int u = q.front(); q.pop();
vis[u] = 1;
for (int v:e[u]){
if (vis[v]) continue;
q.push(v);
}
}
if (vis[t]) cout << "Yes!!!" << endl;
else cout << "No!!!" << endl;
}
}這題其實可以轉成我們剛剛的圖,就是每個點它可以走到點就是它鄰近的四個點,且這幾個點沒有超過範圍,且不是牆
if (nxtx < 1 || nxtx > n || nxty < 1 || nxty > m) continue;
if (vis[nxtx][nxty]) continue;
if (c[nxtx][nxty] == '#') continue;我們求路徑的方法其實很簡單,我們只要在走到新的點時,紀錄我們從哪個位置走過來就好了
然後最後在一直走回去就好了
q.push({nxtx, nxty, d+1});
vis[nxtx][nxty] = 1;
//等一下解釋k是什麼,先別及
if (k == 0) dir[nxtx][nxty] = 'R';
else if (k == 1) dir[nxtx][nxty] = 'L';
else if (k == 2) dir[nxtx][nxty] = 'D';
else if (k == 3) dir[nxtx][nxty] = 'U';int x = e.first, y = e.second;
while (x != s.first || y != s.second){
ans.push_back(dir[x][y]);
if (dir[x][y] == 'R') --y;
else if (dir[x][y] == 'L') ++y;
else if (dir[x][y] == 'U') ++x;
else if (dir[x][y] == 'D') --x;
}
for (int i=ans.size()-1; i>=0; --i) cout << ans[i];如果題目是二維圖,我們有一個小技巧,就是紀錄每個方向它改變的x, y
然後遍歷陣列,加上我們的現在的點,就會是可以走到的點了
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
for (int k=0; k<4; ++k){
int nxtx = x + dx[k], nxty = y + dy[k];
}#include <bits/stdc++.h>
#define endl '\n'
#define maxn 1005
#define int long long
using namespace std;
struct coord{
int x, y, d;
};
int n, m;
pair<int, int> s, e;
char c[maxn][maxn], dir[maxn][maxn];
queue<coord> q;
bool f, vis[maxn][maxn];
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};
vector<char> ans;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=n; ++i){
for (int j=1; j<=m; ++j){
cin >> c[i][j];
if (c[i][j] == 'A') s = {i, j};
if (c[i][j] == 'B') e = {i, j};
}
}
q.push({s.first, s.second, 0});
vis[s.first][s.second] = 1;
while (!q.empty()){
int x = q.front().x, y = q.front().y, d = q.front().d; q.pop();
if (x == e.first && y == e.second){
f = true;
cout << "YES" << endl;
cout << d << endl;
break;
}
for (int k=0; k<4; ++k){
int nxtx = x + dx[k], nxty = y + dy[k];
if (nxtx < 1 || nxtx > n || nxty < 1 || nxty > m) continue;
if (vis[nxtx][nxty]) continue;
if (c[nxtx][nxty] == '#') continue;
q.push({nxtx, nxty, d+1});
vis[nxtx][nxty] = 1;
if (k == 0) dir[nxtx][nxty] = 'R';
else if (k == 1) dir[nxtx][nxty] = 'L';
else if (k == 2) dir[nxtx][nxty] = 'D';
else if (k == 3) dir[nxtx][nxty] = 'U';
}
}
if (f){
int x = e.first, y = e.second;
while (x != s.first || y != s.second){
ans.push_back(dir[x][y]);
if (dir[x][y] == 'R') --y;
else if (dir[x][y] == 'L') ++y;
else if (dir[x][y] == 'U') ++x;
else if (dir[x][y] == 'D') --x;
}
for (int i=ans.size()-1; i>=0; --i) cout << ans[i];
}else cout << "NO" << endl;
}
中等:ZJ c124
困難:
(提示:先多源BFS檢查殭屍可以走到點最快的時間,然後人走的時候檢查就好了)
(提示:他開和關可以視為兩個不同的點,這兩個點可以之間移動,且保證我們只會走過個點一次,因為走回去會浪費時間)
深度優先搜尋
假設我們有一張無權圖,要從A點開始,遍歷整張圖
我們可以像走迷宮,不斷往下走直到死路,然後往回走
因為都是死路,所以一路反回到1
回到點1後,我們找下個點,就會是點4
然後接下來是死路,所以就結束了
最後我們就成功用DFS遍歷整張圖了
時間複雜度為 O(n+m)
並且會發現一個特性:
和BFS不同的是,它沒辦法找最短路徑,但是非常好寫
(等一下會解釋)
void dfs(int x){
vis[x] = 1;
for (int i:e[x]){
if (vis[i]) continue;
dfs(i);
}
}可以看到dfs的扣很短,比bfs好寫
給點與點之間的有向邊
求可不可以從一點走到一點
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 805
using namespace std;
int n, m, s, t;
vector<int> e[maxn];
bool vis[maxn];
bool f;
void dfs(int x){
vis[x] = 1;
for (int i:e[x]){
if (vis[i]) continue;
dfs(i);
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
while (cin >> n >> m){
//__init__
f = false; queue<int> q;
for (int i=1; i<=n; ++i){
vis[i] = 0;
e[i].clear();
}
for (int i=1; i<=m; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
}
cin >> s >> t;
dfs(s);
if (vis[t]) cout << "Yes!!!" << endl;
else cout << "No!!!" << endl;
}
}剩下的技巧和上次教的一樣 (忘記可以往前翻複習一下)
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 1005
#define int long long
using namespace std;
int n, m, cnt; bool maze[maxn][maxn];
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
void dfs(int x, int y){
for (int i=0; i<4; ++i){
if (maze[x+dx[i]][y+dy[i]] == 0) continue;
maze[x+dx[i]][y+dy[i]] = 0;
dfs(x + dx[i], y + dy[i]);
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=n; ++i){
for (int j=1; j<=m; ++j){
char tmp; cin >> tmp;
if (tmp == '#') maze[i][j] = 0;
else maze[i][j] = 1;
}
}
for (int i=1; i<=n; ++i){
for (int j=1; j<=m; ++j){
if (maze[i][j] == 0) continue;
++cnt;
dfs(i, j);
}
}
cout << cnt << endl;
}困難:
abc 448 pD - Interger-duplicated Path
(提示:樹)
abc 435 pD - Reachability Query 2
(提示:黑點數只會增加,不會減少)
很困難:
Educational Codeforces Round 188 - pD Alternating Path
(提示:想想看,我們可以將題目轉移成什麼問題 上面有歐)
並查集
首先我們將每個點都往外連一條有向邊到其他點,並且把他預設成自己
並且將頭定義成 最後走到的點的邊是指向自己的
我們要連的時候,只要把我們那個點的頭 指向另外一個點的頭就好了
舉例來說,我們要把1 2合併
就把2的頭 (2) 的邊指向1的頭 (1)
舉例來說,我們要把2 3合併
就把2的頭 (1) 的邊指向3的頭 (3)
* 我們找頭的方式 只要不斷往下走,
直到走到邊指向自己的就代表找到頭了
(ex: 2 -> 1 -> 3 -> 3,3就會是頭)
* 我們只要確認兩個點是否在同個集合,
只要檢查頭是否相同就好了
(ex: 1和2的頭都是3,故在同個集合內)
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 100005
using namespace std;
int n, t, parent[maxn];
string i1; int i2, i3;
inline void build(){
for (int i=1; i<=n; ++i) parent[i] = i;
}
int find_set(int v) {
if (v == parent[v]) return v;
return find_set(parent[v]);
}
void union_set(int a, int b) {
a = find_set(a);
b = find_set(b);
if (a != b) {
parent[b] = a;
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> t;
build();
while (t--){
cin >> i1 >> i2 >> i3;
if (i1 == "union"){
union_set(i2, i3);
}else{
if (find_set(i2) == find_set(i3)) cout << "YES" << endl;
else cout << "NO" << endl ;
}
}
}每次將小集合併入大集合
設 sa 為大集合大小 , sb為小集合大小
有一個性質為 :
sza+szb>=szb∗2sa+sb>=sb∗2
由於上述性質,我們先把dsu想成一棵樹,
我們每往上走一步,他的子樹的點一定會多
2倍以上,故他最多只會走到O(log n)步
因為我們只在乎他的頭在哪裡,
所以我們在往上找得時候,只要把每個點都指向頭就好了
(就有點像我們把路徑都縮短的概念)
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 100005
using namespace std;
int n, t, parent[maxn], siz[maxn];
string i1; int i2, i3;
inline void build(){
for (int i=1; i<=n; ++i){
parent[i] = i;
siz[i] = 1;
}
}
int find_set(int v) {
if (v == parent[v]) return v;
return parent[v] = find_set(parent[v]); //壓路徑
}
void union_set(int a, int b) {
a = find_set(a);
b = find_set(b);
if (a != b) {
if (siz[a] < siz[b]) swap(a, b);
//make sure that siz[b] < siz[a]
//so b is the smaller set
parent[b] = a;
siz[a] += siz[b];
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> t;
build();
while (t--){
cin >> i1 >> i2 >> i3;
if (i1 == "union"){
union_set(i2, i3);
}else{
if (find_set(i2) == find_set(i3)) cout << "YES" << endl;
else cout << "NO" << endl ;
}
}
}(且邊權一定是正整數)
1. 將1到n的距離存到dist[n],並將dist全部設成無限 (dist[1]=0)
2. A連接B和C,將B,C更新:
可將dist[2]更新為4,dist[3]更新為8 (4<inf, 8<inf)
3. B距離最小,將B相鄰的點都更新
(因為C的8小於4+11,所以不用更新,也不用再加到pq)
並且若我們走過的點,則不用更新
4. C距離最小,所以對C做一樣的事
(A, B走過,不用更新)
5. F距離最小
6. I距離最小
7. D距離最小
8. E距離最小
9. H距離最小
10. J距離最小
當我們確定一個點的距離為最小時(就是他要往外更新的時候),就能確定他是最短距離了
假設有一個更短的距離,那就代表圖上一定
有一個目前距離比他小的
因為距離比較小,就會先被處理掉,
所以我們可以推論出不會有距離比他小的
與假設矛盾,因此目前一定是最短距離
時間複雜度由實作的方式而改變,這裡用的是最常用的方法,是 O((V + E) log V)
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 100005
#define inf 1e18
#define pii pair<int, int>
using namespace std;
int n, m;
vector<pii> e[maxn];
int dist[maxn];
bool vis[maxn];
priority_queue<pii, vector<pii>, greater<pii>> pq;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
int u, v, c; cin >> u >> v >> c;
e[u].push_back({v, c});
}
for (int i=1; i<=n; ++i) dist[i] = inf;
dist[1] = 0;
pq.push({0, 1});
while (!pq.empty()){
int u = pq.top().second; pq.pop();
if (vis[u]) continue;
vis[u] = 1;
for (pii i:e[u]){
int v = i.first, c = i.second;
if (vis[v]) continue;
if (dist[u] + c < dist[v]){
dist[v] = dist[u] + c;
pq.push({dist[v], v});
}
}
}
for (int i=1; i<=n; ++i){
if (i == n) cout << dist[i] << endl;
else cout << dist[i] << " ";
}
}(並且會有負的邊權)
我們先假設一個東西:
我們從開頭走到點的最短路徑最多只會走過n-1個點
(因爲重複走一個點的話就會浪費)
這樣會有例外,等一下會解釋
那我們只要for回圈 n-1 次,每次將每個點他
鄰近的點都更新,就可以找到最短路徑了
(for回圈代表每走一步,並且看看
這步走到這點是否比較短)
來個圖走走看好了 我們一樣先把每個點距離設成無限,把起點設成0
我們走第一步,發現點 2 3 4 可以被更新
我們走第二步,發現點 4 5 可以被更新
我們走第三步,發現點 5 可以被更新
我們走第四步,發現點每個點都不能被更新
走完n-1步之後,就可以確保每個點都是最短距離了
而這樣的時間複雜度就會是 O(n*m)
就是如果我們有負環怎麼辦?
(舉例來說 2 4 3 2 爲一個負環,我們不斷繞的話,
我們的距離會無限的變小)
因爲我們剛剛保證最短距離一定在n-1步可以達到
唯一的例外就是負環
我們只要檢查如果第n步還有更新的話,就帶表有負環
扣
//e 存每條邊的資訊
for (int i=1; i<=n; ++i){
bool flag = false;
for (int j=1; j<=m; ++j){
int u = e[i].u, v = e[i].v, d = e[i].d;
if (dist[u] == inf) continue;
if (dist[u] + d < dist[v]){
flag = true;
dist[v] = dist[u] + d;
}
}
if (!flag) break;
if (i == n && flag) f = true;
}
//f 會代表有沒有負環
因爲我們要找最大的,所以只要把羅輯改成找最大的就好了,剩下羅輯相同
並且在檢查負環時,如果走的到n才會影響解果,所以dfs檢查就好了
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 200005
#define pii pair<int, int>
#define inf 1e18
using namespace std;
struct edge{
int u, v, d;
};
int n, m, dist[maxn];
edge e[maxn];
vector<int> w[maxn];
bool f;
bool vis[maxn];
void dfs(int x){
vis[x] = 1;
for (int i:w[x]){
if (vis[i]) continue;
dfs(i);
}
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
cin >> e[i].u >> e[i].v >> e[i].d;
w[e[i].u].push_back(e[i].v);
}
for (int i=1; i<=n; ++i) dist[i] = -inf;
dist[1] = 0;
for (int i=1; i<=n; ++i){
bool flag = false;
for (int j=1; j<=m; ++j){
int u = e[j].u, v = e[j].v, d = e[j].d;
if (dist[u] == -inf) continue;
if (dist[u] + d > dist[v]){
flag = true;
dist[v] = dist[u] + d;
if (i==n) dfs(v);
}
}
if (!flag) break;
if (i == n && flag) f = true;
}
if (f && vis[n]) cout << -1 << endl;
else cout << dist[n] << endl;
}他的概念就是dp
大概的想法是,每次多考慮一個點,把每個走的方法都找到
我們選定兩點 i, j
定義 為從點 i 到點 j,
目前走了k步,他的最短距離
我們可以列出一個轉移式 :
概念有點像是,
我們試圖在 i, j 的路徑中加入k,看看會不會距離更小
//dis[0] 預設爲距離矩陣 (就是鄰接陣矩存距離)
for(int k=1;k<=n;++k){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
dis[k][i][j] =
min(dis[k-1][i][j], dis[k-1][i][k]+dis[k-1][k][j]);
}
}
}//dis[0] 預設爲距離矩陣 (就是鄰接陣矩存距離)
for(int k=1;k<=n;++k){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
dis[i][j] = min(dis[i][j], dis[i][k]+dis[k][j]);
}
}
}最後的表格就是 i到j 的最短距離了!
時間複雜度爲 O(n^3)
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 505
#define inf 1e18
#define int long long
using namespace std;
int n, m, q, dist[maxn][maxn];
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m >> q;
for (int i=1; i<=n; ++i){
for (int j=1; j<=n; ++j){
if (i!=j) dist[i][j] = inf;
}
}
for (int i=1; i<=m; ++i){
int u, v, w; cin >> u >> v >> w;
dist[u][v] = min(dist[u][v], w);
dist[v][u] = min(dist[v][u], w);
}
for (int k=1; k<=n; ++k){
for (int i=1; i<=n; ++i){
for (int j=1; j<=n; ++j){
dist[i][j] = min(dist[i][j], dist[i][k]+dist[k][j]);
}
}
}
while (q--){
int a, b; cin >> a >> b;
if (dist[a][b] != inf) cout << dist[a][b] << endl;
else cout << -1 << endl;
}
}最小生成樹
生成樹是對於一個連通的無向圖
n 點 m 邊
有 n-1個邊
並且不存在環
在所有的生成樹中,所選邊權總和最小的
就是最小生成樹 MST
所有邊權皆不同 -> 唯一
有邊權相同 -> 可能唯一
有可能最後最小的總和會相同
1 - 一個環上的邊權最大值,必不屬於MST
2 - 一個cut上的最小邊權必屬於MST
cut 簡單來說,就是把一個連通塊選
一些邊切掉,把他分成兩個連通塊
我們用剛剛學到的一個性質:
一個環上的邊權最大值,必不屬於MST
我們只要把邊由小到大加到圖裡面
如果遇到環的時候,因為樹裡面不會有環,
並且因為我們是由小到大加到圖裡,
所以一個環中最大的那個點不會被加到MST裡面
我們加入圖的時候會遇到兩種情況:
1 - 不為環
就直接加到圖裡就好了
2 - 為環
有上述得知,這個邊不要加到圖裡
用並查及!
如果已經在同個集合內,那就代表是一個環了!
我們在遇到2-3的邊時,因為他們已經在同個集合內了,所以不加入,剩下的以此類推:
時間複雜度:
O(E log E + Eα(V))
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define maxn 200005
using namespace std;
struct edge{
int u, v, w;
};
int n, m, parent[maxn], siz[maxn], cnt;
vector<edge> e; bool f=true;
bool comp(edge a, edge b){
return a.w < b.w;
}
inline void build(){
for (int i=1; i<=n; ++i){
parent[i] = i;
siz[i] = 1;
}
}
int find_set(int v){
if (parent[v] == v) return v;
return parent[v] = find_set(parent[v]);
}
void union_set(int a, int b){
if (siz[a] < siz[b]) swap(a, b);
parent[b] = a;
siz[a] += siz[b];
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
int u, v, d; cin >> u >> v >> d;
e.push_back({u, v, d});
}
build();
sort(e.begin(), e.end(), comp);
for (int i=0; i<m; ++i){
int v = e[i].v, u = e[i].u, w = e[i].w;
int a = find_set(v), b = find_set(u);
if (a != b){
cnt += w;
union_set(a, b);
}
}
cout << cnt << endl;
}
我們從一個點出發,慢慢的將每個點加入這個MST
我們每次找到目前所有MST的點,往外連的邊(連到非MST)中
邊權最小的那個,並將那條邊以及連到的點加入MST
由剛剛學到的性質二得知:一個cut上的最小邊權必屬於MST
舉例來說:假設我們目前的圖長這樣
那我們接下來可以選擇的邊有這些
這些可以選的點都是在cut上,因此我們要選最小的那個:
時間複雜度:
O(E +V log V)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define maxn 100005
int n, m, sum; vector<pii> e[maxn];
bool vis[maxn];
priority_queue<pii, vector<pii>, greater<pii>> pq;
//pq.first -> the edge of the nodes in the MST, pq.second -> the node
//v[a].first -> node beside a, v[a].second -> dis between a and first
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for(int i=1;i<=m;++i){
int u, v, d; cin >> u >> v >> d;
e[u].push_back({v, d});
e[v].push_back({u, d});
}
pq.push({0, 1});
while(!pq.empty()){
int u = pq.top().second, w = pq.top().first; pq.pop();
if(vis[u]) continue;
sum += w;
vis[u] = 1;
for(auto i:e[u]){
int v = i.first, d = i.second;
if(!vis[v]) pq.push({d, v});
}
}
cout << sum << endl;
}拓撲排序
我們的目標是要找出一個由點組成的排序 (一串點的陣列)
而這個排序會有幾個條件要符合,就是哪個點要在哪個點前面
舉例來說:我們有一個2要在3前面的條件,那在topo sort後
的這串點中,2必出現在3之前
因為一個 DAG 中一定有 Source (只有出沒有進的 node)
和 Sink(只進不出的 node)
簡單來說 Source的 in degree 是 0,Sink的 out degree 是 0
所以一個拓撲順序都是從 source 開始,從 sink 結束。
時間複雜度:
O(n + m)
for (int i=1; i<=n; ++i){
if (in[i] == 0) q.push(i);
}
while (!q.empty()){
int u = q.front(); q.pop();
ans.push_back(u);
for (int v:e[u]){
--in[v];
if (in[v] == 0) q.push(v);
}
}
//ans 是 topo sort 的結果給予我們一個有向圖 並且要找到一個topo sort的順序,如果沒有就輸出 "IMPOSSIBLE"
這只是一般的topo sort題目,只要多判斷有沒有環就好了
我們只要判說最後的答案有沒有包含全部的點,沒有的話就代表有環
#include <bits/stdc++.h>
#define endl '\n'
#define maxn 100005
#define int long long
using namespace std;
int n, m, in[maxn]; vector<int> e[maxn];
queue<int> q; vector<int> ans;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
++in[v];
}
for (int i=1; i<=n; ++i){
if (in[i] == 0) q.push(i);
}
//topological sort
while (!q.empty()){
int u = q.front(); q.pop();
ans.push_back(u);
for (int v:e[u]){
--in[v];
if (in[v] == 0) q.push(v);
}
}
if (ans.size() != n) cout << "IMPOSSIBLE" << endl;
else{
for (int i=0; i<ans.size(); ++i){
if (i==ans.size()-1) cout << ans[i] << endl;
else cout << ans[i] << " ";
}
}
}給予一個DAG,存找1到n有幾種走法,mod 1e9+7
假這我們有點 v,dp[v] 代表我到目前從1到v有幾種走法,
我們可以推論出一個dp式:
對於所有有向邊指向 v 的點 u,dp[v] += dp[u]
但是我們會遇到一個問題:
就是我們在更新 v 的時後,u 有可能還沒被更新
接下來會發現,我們只要由topo sort的順序來更新,就能保證 v 在更新的時候,u 已經被更新到了
dp 更新時候是 O(n+m),在這裡是往後更新,因為就不用存新的圖
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define maxn 200005
using namespace std;
int n, m, dp[maxn];
int in[maxn]; vector<int> e[maxn], t;
queue<int> q;
const int p = 1e9+7;
signed main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
for (int i=1; i<=m; ++i){
int u, v; cin >> u >> v;
e[u].push_back(v);
++in[v];
}
for (int i=1; i<=n; ++i){
if (in[i] == 0) q.push(i);
}
while (!q.empty()){
int x = q.front(); q.pop();
t.push_back(x);
for (int v:e[x]){
--in[v];
if (in[v] == 0) q.push(v);
}
}
dp[1] = 1;
for (int i:t){
for (int v:e[i]) dp[v] = (dp[v] + dp[i]) % p;
}
cout << dp[n] << endl;
}
(那你要來上放課的進階圖論嗎 :P)
(也是我教 :P)