邻接表示意图:
有向图的拓扑序列(邻接表)
给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环,请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1≤n,m≤105
任意边长的绝对值不超过 10000
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int e[N],ne[N],h[N],idx;
int d[N],top[N],cnt=0;
int n,m;
// e,ne,h,idx 邻接表模板
// d 代表每个元素的入度
// top是拓扑排序的序列,cnt代表top中有多少个元素
void add(int a,int b){
e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;
}
bool topsort(){
queue<int> q;
int t;
for(int i = 1;i <= n; ++i) if(d[i] == 0) q.push(i);
while(q.size()){
t = q.front();q.pop();
top[++cnt] = t;//加入到拓扑序列中
for(int i = h[t];i != -1; i = ne[i]){//遍历t点的出边
int j = e[i];
d[j] --;
if(d[j] == 0) q.push(j);
}
}
if(cnt < n) return 0;
//如果拓扑序列存在,那么所有点都会被加到拓扑序列里面
if(cnt == n)return 1;
}
int main(){
cin >> n >> m;
memset(h,-1,sizeof h);
int a,b;
while(m--){
cin >> a >> b;
add(a,b);
d[b] ++;//a->b,b的入度++
}
if(topsort() == 0) cout << "-1";
else {
for(int i = 1;i <= n; ++i)
cout << top[i] <<" ";
}
return 0;
}
最短路
- n:点数,m:边数
- 稠密图(边多):朴素Dijkstra,邻接矩阵即可
- 稀疏图(边少):堆优化Dijkstra
Dijkstra-朴素 O ( n 2 ) O(n^2) O(n2)
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值,请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1
1 ≤ n ≤ 500 1≤n≤500 1≤n≤500
1 ≤ m ≤ 1 0 5 1≤m≤10^5 1≤m≤105
任意边长的绝对值不超过 10000
- 在最短路里,如果有>0的自环,这个自环显然不会出现在最短路里面
- 在Dijkstra里面,如果有>=0的自环,在松弛操作时没有影响,所以不用管
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxv=505;
int n,m,vis[maxv],d[maxv];
int g[maxv][maxv];
int dij(){
memset(d,0x3f,sizeof d);//便于接下来的操作
d[1]=0;//确定起点到自己的距离为0
for(int i=1;i<=n;i++){
int t=-1;
for(int j=1;j<=n;j++)
if(!vis[j] && (t==-1||d[t]>d[j]))t=j;
vis[t]=1;
for(int j=1;j<=n;j++)
d[j]=min(d[j],d[t]+g[t][j]);//松弛操作
}
if(d[n]==0x3f3f3f3f)return -1;
else return d[n];
}
int main(){
cin>>n>>m;
memset(g,0x3f,sizeof g);
int a,b,c;
while(m--){
scanf("%d%d%d",&a,&b,&c);
g[a][b]=min(g[a][b],c);//重边取min即可
}
int cnt=dij();
printf("%d",cnt);
}
Dijkstra-堆优化 O ( m l o g m ) O(mlogm) O(mlogm)
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值,请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1
1 ≤ n , m ≤ 1.5 ∗ 1 0 5 1≤n,m≤1.5*10^5 1≤n,m≤1.5∗105
任意边长的绝对值不超过 10000
代码:
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 150010;
//稀疏图用邻接表来存
int h[N], e[N], ne[N], idx;
int w[N]; //存权重
int d[N];
bool st[N]; //true说明这个点的最短路径已经确定
int n, m;
//>=0的自环在松弛的时候一定不满足条件,会自动失效
//有重边也不要紧,更新多次后等价于自动筛出了最短重边
void add(int x, int y, int c){
w[idx] = c; e[idx] = y; ne[idx] = h[x]; h[x] = idx++;
}
int dijkstra(){
memset(d, 0x3f, sizeof(d));
d[1] = 0;
priority_queue<PII> heap;//大根堆
heap.push({ 0, 1 });//顺序不能倒,这里显然要根据距离排序
while(heap.size()){
PII k = heap.top();heap.pop();//取不在集合S中距离最短的点
int ver = k.second, dis = -k.first;
//如果这个点已经出来过了,说明这个点是冗余备份,舍弃即可
if(st[ver]) continue;
st[ver] = true;//放入集合S中,代表这个源点到这个点的最短路已经确定
for(int i = h[ver]; i != -1; i = ne[i]){
int j = e[i];//i代表ver点的一个出边,e是这个边的终点
if(d[j] > dis + w[i]){
d[j] = dis + w[i];
heap.push({-d[j], j});
}
}
}
if(d[n] == 0x3f3f3f3f) return -1;
else return d[n];
}
int main(){
memset(h, -1, sizeof(h));
scanf("%d%d", &n, &m);
while(m--){
int x, y, c;
scanf("%d%d%d", &x, &y, &c);
add(x, y, c);
}
cout << dijkstra() << endl;
return 0;
}
习题:
题意:
给定 P 个点, Q条有向边,经过每条边都有一个花费,现在需要先将P-1个人从1号点分别送往P-1个点上(即除1号点外其他每一个点上都必须有一个人),然后在将这P-1个人送回1号点,问最少的总花费
思路:
思维活跃一些 , 正反建图各跑一遍就好啦
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
#define ll long long
const int N=1e6+5,M=1e6+5;
int n,m;
inline ll in() { char ch = getchar();ll 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 x * f; }
int Head[N],ver[M],Next[M],edge[M],tot=1;
int Head1[N],ver1[M],Next1[M],edge1[M],tot1=1;
void add(int x, int y, int z){
//建正图
tot++;
ver[tot]=y;edge[tot]=z;Next[tot]=Head[x];Head[x]=tot;
//建反图
tot1++;
ver1[tot1]=x;edge1[tot1]=z;Next1[tot1]=Head1[y];Head1[y]=tot1;
}
int dis[N],dis1[N];
bool vis[N];
priority_queue<pair<int,int> >q;
void dijstra1(){
memset(dis,0x3f,sizeof(dis));
while(q.size())q.pop();
q.push(make_pair(0,1));
dis[1]=0;
while(q.size()){
int u=q.top().second;
q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=Head[u];i;i=Next[i]){
int v=ver[i];
if(dis[v]>dis[u]+edge[i]){
dis[v]=dis[u]+edge[i];
q.push(make_pair(-dis[v],v));
}
}
}
}
void dijstra2(){
memset(dis1,0x3f,sizeof(dis1));
memset(vis,0,sizeof(vis));
while(q.size())q.pop();
q.push(make_pair(0,1));
dis1[1]=0;
while(q.size()){
int u=q.top().second;
q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=Head1[u];i;i=Next1[i]){
int v=ver1[i];
if(dis1[v]>dis1[u]+edge1[i]){
dis1[v]=dis1[u]+edge1[i];
q.push(make_pair(-dis1[v],v));
}
}
}
}
int main(){
int t=1;
while (t--)
{
memset(vis,0,sizeof vis);
memset(Head,0,sizeof Head);
memset(Head1,0,sizeof Head1);
tot=1,tot1=1;
n=in();m=in();
for(int i=1;i<=m;i++){
int x=in(),y=in(),z=in();
add(x, y, z);
}
dijstra1();
dijstra2();
int ans=0;
for(int i=2;i<=n;i++)ans+=dis[i]+dis1[i];
cout<<ans<<endl;
}
}
Bellman_ford O ( n m ) O(nm) O(nm)
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数,请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible,注意:图中可能存在负权回路 。
1 ≤ n , k ≤ 500 1≤n,k≤500 1≤n,k≤500
1 ≤ m ≤ 10000 1≤m≤10000 1≤m≤10000
任意边长的绝对值不超过 10000
注:
在下面代码中,判断从1号点走到n号点否是无穷大距离时,需要进行if(t > INF/2)
判断,而并非是if(t == INF)
判断,原因是INF是一个确定的值,并非真正的无穷大,会随着其他数值而受到影响,t大于某个与INF相同数量级的数即可
代码:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 10010;
struct Edge{
int a,b,w;
} e[M];//边
int dist[N];
int back[N];//备份数组防止串联
int n, m, k;//k代表最短路径最多包涵k条边
int bellman_ford(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;//初始化,注意别忘了
for (int i = 0; i < k; i++) {//最多经过k条边
memcpy(back, dist, sizeof dist);
for (int j = 0; j < m; j++) {//遍历所有边,注意循环节是m
int a = e[j].a, b = e[j].b, w = e[j].w;
dist[b] = min(dist[b], back[a] + w);
//使用backup:避免给a更新后立马更新b
}
}
//可能存在 ∞把 ∞更新的情况
if (dist[n] > 0x3f3f3f3f / 2) return -1;
else return dist[n];
}
int main(){
scanf("%d%d%d", &n, &m, &k);
int a, b, w;
for (int i = 0; i < m; i++){
scanf("%d%d%d", &a, &b, &w);
//傻瓜式存图,边与边之间可以没有任何关联
e[i] = {a, b, w};
}
int res = bellman_ford();
if(res == -1) puts("impossible");
else cout << res;
return 0;
}
Spfa O ( k m ) O(km) O(km)~ O ( n m ) O(nm) O(nm)
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数,请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible,数据保证不存在负权回路。
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1≤n,m≤105
图中涉及边长绝对值均不超过 10000
- spfa对bellman—ford中对所有边进行松弛操作进行了优化,原因是在bellman—ford中,即使该点的最短距离尚未更新过,但还是需要用尚未更新过的值去更新其他点,由此可知,该操作是不必要的,我们只需要找到更新过的值去更新其他点即可
- spfa也能解决权值为正的图的最短距离问题,且一般(不被卡)的情况下比Dijkstra算法还好
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5+10;
int n, m; // n个点m条边
int h[N], e[N], w[N], ne[N], idx; // 邻接表存储所有边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N];//spfa核心,存储每个点是否在队列中
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int spfa(){
memset(dist, 0x3f, sizeof dist);dist[1] = 0;//初始化
queue<int> q;
q.push(1);
st[1] = true;//1号点在队列里
while(q.size()){
auto t = q.front(); q.pop();st[t] = false;//取出队头
for(int i = h[t]; i != -1; i = ne[i]){//扫描所有出边
int j = e[i];//出边终点
if(dist[j] > dist[t] + w[i]){
dist[j] = dist[t] + w[i];//更新到j点的最短距离
if(!st[j]){//如果不在队列里
q.push(j);st[j] = true;
}
}
}
}
if(dist[n] == 0x3f3f3f3f)return -1;// 1 ~ n不连通,返回-1
return dist[n];//返回第n个点到源点的最短距离
}
int main(){
cin >> n >> m;
memset(h, -1, sizeof h);//初始化所有表头
int a, b, c;
while(m--){
cin >> a >> b >> c;
add(a, b, c);
}
int t = spfa();
if(t == -1) puts("impossible");
else cout << t << endl;
return 0;
}
spfa判断负环 O ( k m ) O(km) O(km)~ O ( n m ) O(nm) O(nm)
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数,请你判断图中是否存在负权回路,如果图中存在负权回路,则输出 Yes,否则输出 No
1 ≤ n ≤ 2000 1≤n≤2000 1≤n≤2000
1 ≤ m ≤ 10000 1≤m≤10000 1≤m≤10000
图中涉及边长绝对值均不超过 10000
每次做一遍spfa()一定是正确的,但时间复杂度较高,可能会超时。初始时可以将所有点插入队列中
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2010,M=10010;
int h[N],e[M],ne[M],w[M],idx;
bool st[N];
int d[N],cnt[N];//cnt数组表示到达当前这个点最短路的边数
int n,m;
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa(){
memset(d,0,sizeof(d));//本题可以不做初始化
memset(cnt,0,sizeof(cnt));
memset(st,false,sizeof(st));
queue<int> q;
for(int i=1;i<=n;i++){//判整个图的负环要将每个节点都加入
st[i]=true;q.push(i);
}
while(!q.empty()){
int t=q.front();q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(d[j]>d[t]+w[i]){
d[j]=d[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j]){
st[j]=true;q.push(j);
}
}
}
}
return false;
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof(h));
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
if(spfa()) puts("Yes");
else puts("No");
}
Floyd O ( n 3 ) O(n^3) O(n3)
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数,再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出impossible,数据保证图中不存在负权回路
1 ≤ n ≤ 200 1≤n≤200 1≤n≤200
1 ≤ k ≤ n 2 1≤k≤n^2 1≤k≤n2
1 ≤ m ≤ 20000 1≤m≤20000 1≤m≤20000
图中涉及边长绝对值均不超过 10000
思路:
f
[
i
,
j
,
k
]
f[i, j, k]
f[i,j,k]表示从 i 走到 j 的路径上除 i 和 j 点外只经过1到 k 的点的所有路径的最短距离。那么
f
[
i
,
j
,
k
]
=
m
i
n
(
f
[
i
,
j
,
k
−
1
)
,
f
[
i
,
k
,
k
−
1
]
+
f
[
k
,
j
,
k
−
1
]
f[i, j, k] = min(f[i, j, k - 1), f[i, k, k - 1] + f[k, j, k - 1]
f[i,j,k]=min(f[i,j,k−1),f[i,k,k−1]+f[k,j,k−1]
因此在计算第 k 层的
f
[
i
,
j
]
f[i, j]
f[i,j]的时候必须先将第 k - 1 层的所有状态计算出来,所以需要把 k 放在最外层
判断从a到b是否是无穷大距离时,需要进行if(t > INF/2)
判断,而并非是if(t == INF)
判断,原因是INF是一个确定的值,并非真正的无穷大,会随着其他数值而受到影响,t大于某个与INF相同数量级的数即可
代码:
#include <iostream>
using namespace std;
const int N = 210, M = 2e+10, INF = 1e9;
int n, m, k, x, y, z;
int d[N][N];
void floyd() {//d[i,j,k]:从i出发,只经过1-k这几个中间点,到j的最短距离
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main() {
cin >> n >> m >> k;
for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)
if(i == j) d[i][j] = 0;//图中不存在负权回路,所以自环一定为正,直接删去即可
else d[i][j] = INF;
while(m--) {
cin >> x >> y >> z;
d[x][y] = min(d[x][y], z);//重边
}
floyd();
while(k--) {
cin >> x >> y;
if(d[x][y] > INF/2) puts("impossible");
//由于有负权边存在所以约大过INF/2也很合理
else cout << d[x][y] << endl;
}
return 0;
}
最小生成树
图片来自acwing:
-
n
:点数,
m
:边数
n:点数,m:边数
n:点数,m:边数
朴素Prim O ( n 2 ) O(n^2) O(n2)
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数,求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
1 ≤ n ≤ 500 1≤n≤500 1≤n≤500
1 ≤ m ≤ 1 0 5 1≤m≤10^5 1≤m≤105
图中涉及边的边权的绝对值均不超过 10000
思路:
S:当前已经在联通块中的所有点的集合
联系:Dijkstra算法是更新到起始点的距离,Prim是更新到集合S的距离
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], dist[N];//dist存储其他点到S的距离
bool st[N];
int prim() {
//如果图不连通返回INF, 否则返回res
memset(dist, INF, sizeof dist);
int res = 0;
for(int i = 0; i < n; i++) {
int t = -1;
for(int j = 1; j <= n; j++)//寻找离集合S最近的点
if(!st[j] && (t == -1 || dist[t] > dist[j]))t = j;
//判断是否连通(有无最小生成树),注意i为1时dist[t]为INF
if(i && dist[t] == INF) return INF;
if(i) res += dist[t];//先累加再更新可以避免自环
st[t] = true;
//松弛操作
for(int j = 1; j <= n; j++) dist[j] = min(dist[j], g[t][j]);
}
return res;
}//最小生成树定义上不允许自环
int main() {
cin >> n >> m;
int u, v, w;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
g[i][j] = INF;
while(m--) {
cin >> u >> v >> w;
g[u][v] = g[v][u] = min(g[u][v], w);
}
int t = prim();//临时存储防止执行两次函数导致最后仅返回0
if(t == INF) puts("impossible");
else cout << t << endl;
}
Kruskal O ( m l o g m ) O(mlogm) O(mlogm)
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数,求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1≤n≤105
1 ≤ m ≤ 2 ∗ 1 0 5 1≤m≤2*10^5 1≤m≤2∗105
图中涉及边的边权的绝对值均不超过 1000
思路:
Kruskal算法对应图: 稀疏图
- 将所有边按权重从小到大排序共 O ( m l o g m ) O(mlogm) O(mlogm)次
- 枚举每条边a,b,权重c共O ( m ) (m) (m)次
- 枚举过程中
if(a,b两点不连通){将a,b边加入集合中}
- 3操作可用并查集实现
- 需要使用变量cnt来记录加进集合的边数,若cntくn-1表示不能遍历所有点
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct Edge {
int a, b, w;
bool operator<(const Edge &e) const {
return w < e.w;
}
} es[M];
int find(int x) {
return (p[x] == x) ? p[x] : p[x] = find(p[x]);
}
int kruskal() {
int cnt = 0, res = 0;
sort(es, es + m);
for (int i = 1; i <= n; i++) p[i] = i;
for (int i = 0; i < m; i++) {
int a = es[i].a, b = es[i].b, w = es[i].w;
a = find(a), b = find(b);
if (a != b) {
p[a] = b;
res += w;
cnt++;
}
}
if (cnt < n - 1) return INF;
else return res;
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
es[i] = {a, b, w};
}
int t = kruskal();
if (t == INF) cout << "-1";
else cout << t;
}
二分图
染色法判定二分图
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,请你判断这个图是否是二分图,如果给定图是二分图,则输出 Yes,否则输出 No
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1≤n,m≤105
二分图:
- 将所有点分成两个集合,使得所有边只出现在集合之间
- 一定不含有奇数环,可能包含长度为偶数的环, 不一定是连通图
- 可以用染色法判定
代码:
#include<bits/stdc++.h>
#define f(i,a,b) for(int i=a;i<b;i++)
#define ff(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int N=1e5+5;
//无向图, 所以最大边数是2倍,否则会WA
int h[N],ne[N*2],e[N*2],idx=0;
int st[N];
int n,m;
void add(int x,int y){
e[idx]=y,ne[idx]=h[x],h[x]=idx++;
}
int dfs(int x,int t){
st[x]=t;
for(int i=h[x];~i;i=ne[i]){
int j=e[i];
if(!st[j]){
if(!dfs(j,3-t))
return 0;
}
else if(st[j]==t)return 0;
}
return 1;
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
f(i,0,m){
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
}
int fl=1;
ff(i,1,n){
if(!st[i]){
if(!dfs(i,1)){
fl=0;
break;
}
}
}
if(fl)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
二分图的最大匹配
给定一个二分图,其中左半部包含 n 1 n_{1} n1 个点(编号 1∼ n 1 n_{1} n1),右半部包含 n 2 n_{2} n2 个点(编号 1∼ n 2 n_{2} n2),二分图共包含 m 条边,数据保证任意一条边的两个端点都不可能在同一部分中,请你求出二分图的最大匹配数
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数
代码:
#include<bits/stdc++.h>
#define f(i,a,b) for(int i=a;i<b;i++)
#define ff(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int N=1e5+5;
int e[N],ne[N],h[N],idx=0;
int st[N],match[N];
int n1,n2,m;
void add(int x,int y){
e[idx]=y,ne[idx]=h[x],h[x]=idx++;
}
bool find(int x){
for(int i=h[x];~i;i=ne[i]){
int j=e[i];
if(!st[j]){
st[j]=1;
if(!match[j] || find(match[j])){
match[j]=x;
return 1;
}
}
}
return 0;
}
int main(){
cin>>n1>>n2>>m;
memset(h,-1,sizeof h);
f(i,0,m){
int a,b;
cin>>a>>b;
add(a,b);
}
int res=0;
ff(i,1,n1){
memset(st,0,sizeof st);
if(find(i))res++;
}
cout<<res<<endl;
}
DAG的最小路径点覆盖
DAG (Directed acyclic graph)即有向无环图
最小路径覆盖:针对一个DAG,用最少条互不相交路径,覆盖所有点(其中互不相交是指点不重复)
结论:最小路径点覆盖(最小路径覆盖) = 总点数 - 最大匹配
最小路径重复点覆盖:在最小路径覆盖问题的基础上,去掉互不相交(点可以重复了)
结论:记原图G,求传递闭包后的图G’,则G的最小路径重复点覆盖=G’的最小路径覆盖
题目:
输入数据的第一行是两个整数 N 和 M,接下来 M 行,每行两个整数 x,y,表示一条从 x 到 y 的有向道路,输出一个整数,即最小路径重复点覆盖个数
N ≤ 200 , M ≤ 30000 N≤200,M≤30000 N≤200,M≤30000
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e2+5;
int n, m;
int match[N], res;
bool st[N], g[N][N];
bool dfs(int x) {
for (int i = 1; i <= n; i ++) {
if (g[x][i] && !st[i]) {
st[i] = 1;
if (match[i] == -1 || dfs(match[i])) {
match[i] = x;
return 1;
}
}
}
return 0;
}
int main() {
memset(match, -1, sizeof match);
cin >> n >> m;
while (m --) {
int u, v;
cin >> u >> v;
g[u][v] = 1;
}
for (int k = 1; k <= n; k ++)
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
g[i][j] |= g[i][k] & g[k][j];
for (int i = 1; i <= n; i ++) {
memset(st, 0, sizeof st);
if (dfs(i)) res ++;
}
cout << n - res << endl;
}