文章目录
全新的图论刷题计划~(普及与普及+)
蒟蒻想好好刷题 😦 也作为平时蒟蒻luogu刷题的一个记录吧!
前言:该博客刷题主要涉及的知识点:最短路、拓扑排序、最小生成树、二分图、图的遍历,从基础开始~
题目1:P5318
基础的图的dfs和bfs,略过
题目2:P3916 图的遍历 (反向建图)
P3916
可以直接建图后暴力dfs,但是只能有20分。
考虑见反图,每次dfs到遍历过的点都会跳过,所以复杂度O(n),从后往前遍历,即for(i = n->1),保证每个点只会最先被编号最大的点遍历到,而由于是反图,所以一定是编号最大的点,用ans数组保存即可。
AC code:链式前向星
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1e5+10;
int e[N],ne[N],h[N],idx;
int ans[N];
void dfs(int u,int id)
{
if(ans[u]) return ;
ans[u] = id;
for(int i = h[u];i!=-1;i=ne[i])
{
int j = e[i];
dfs(j,id);
}
return ;
}
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int main()
{
int n,m;
cin>>n>>m;
memset(h, -1, sizeof h);
while(m--){
int a,b;
cin>>a>>b;
add(b, a);
}
for(int i = n;i>=1;i--)
{
dfs(i,i);
}
for(int i = 1;i<=n;i++)
{
cout<<ans[i]<<" ";
}
return 0;
}
或者也可以写成邻接表形式,(个人觉得邻接表形式对于字典序问题更好处理,比如P5318)
void dfs(int u,int id)
{
if(ans[u]) return ;
ans[u] = id;
for(int i = 0;i<p[u].size();i++)
{
dfs(p[u][i],id);
}
return ;
}
int main()
{
int n,m;
cin>>n>>m;
while(m--){
int a,b;
cin>>a>>b;
p[b].push_back(a);
}
for(int i = n;i>=1;i--)
{
dfs(i,i);
}
for(int i = 1;i<=n;i++)
{
cout<<ans[i]<<" ";
}
}
题目3:P2661 信息传递(dfs求最小环)
P2661
参考了一下题解,很多是并查集写的,仿照第二篇题解给出dfs求最小环的方法
//dfs判最小环 O(n)
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 4e5+10;
int e[N],ne[N],h[N],idx;
int cnt[N];//记录每轮点的编号
int t,ans = 1e9;
int d[N];//记录入度
bool st[N];//已经搜过
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void dfs(int u)
{
//只要是搜过的环都不会再搜索了
if(st[u])
{//已经搜过
return ;
}
if(cnt[u])
{
ans = min(ans,t-cnt[u]);
return ;
}
cnt[u] = t++;
for(int i = h[u];i!=-1;i = ne[i])
{
int j = e[i];
dfs(j);
//在搜索后再把当前点标记
//如果在搜索前就标记,会导致无法对ans进行计数,永远进入不了if(cnt[u])这个判断
st[j] = true;
}
return ;
}
int main()
{
memset(h, -1, sizeof h);
int n;
cin>>n;
for(int i = 1;i<=n;i++)
{
int x;
cin>>x;
add(i,x);//建图
d[x]++;
}
for(int i = 1;i<=n;i++)
{
t = 0;//每一次dfs前 t都需要更新
if(!d[i])//不写这个判断也可以
dfs(i);
}
cout<<ans;
return 0;
}
题目4:P1330 封锁阳光大学(二分染色)
P1330
这里其实和染色法判二分图有点像,但是要注意我们需要选择最少的河蟹来控制道路,所以要用两个变量来计数,每次贡献ans都选较小的那个来贡献。
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 1e4+10;
const int M = 1e5+10;
int h[N], e[M], ne[M], idx;
int color[N],ans;
int cnt;
int dout[N],din[N];
int sum1,sum2;
void add(int a, int b) // 添加一条边a->b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool dfs(int u,int c)
{
color[u] = c;
if(c==1)
sum1++;
if(c==2)
sum2++;
for(int i = h[u];i!=-1;i = ne[i])
{
int j = e[i];
if(!color[j])
{
if(!dfs(j,3-c))
{
return false;
}
}
else if(color[j]==c)
return false;
}
return true;
}
int main()
{
memset(h, -1, sizeof h);
cin>>n>>m;
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
}
bool flag = true;
//应该注意每一个连通块都应该取最小的染色数量
//以此来达到最少只河蟹封锁道路的目的
//所以每一次dfs时需要用两个变量来计数
//每一次都取较小的那个值加进来
for(int i = 1;i<=n;i++)
{
sum1 = 0,sum2 = 0;
if(!color[i])
{
if(!dfs(i,1))
{
flag = false;
break;
}
ans+=min(sum1,sum2);
}
}
if(!flag)
{
cout<<"Impossible";
}
else
{
cout<<ans;
}
}
题目5:P4779 【模板】单源最短路径(标准版)
堆优化的dijiskra模板题~
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
const int N = 1e6+10;
int n,m,s;
int h[N],w[N],e[N],ne[N],idx;
int dist[N];
bool st[N];
void add(int a,int b,int c)
{
w[idx] = c;
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int dijkstra()
{
memset(dist,0x3f3f3f3f,sizeof dist);
dist[s] = 0;
priority_queue<PII,vector<PII>,greater<PII>>heap;
heap.push({0,s});//0表示距离为0,1表示当前节点编号为1
/*
个人理解:其实这里就是用堆来优化去找最近点的过程
*/
while(heap.size())
{
auto t = heap.top();
heap.pop();
int dis = t.first,id = t.second;
if(st[id]) continue;
st[id] = true;
for(int i = h[id]; i !=-1;i = ne[i])
{//这里用最近点去更新这个点能直接到达的所有点
int j = e[i];
if(dist[j]>dis+w[i])
{
dist[j] = dis+w[i];
if(!st[j])
heap.push({dist[j],j});
//不要忘记把更新后的点加入到堆里面去
}
}
}
}
int main()
{
cin>>n>>m>>s;
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
dijkstra();
for(int i = 1;i<=n;i++)
{
cout<<dist[i]<<' ';
}
}
题目6:P2853 [USACO06DEC]Cow Picnic S
P2853
需要好好理解题意,如果让牧场找奶牛,是不好判断的,但是让奶牛找牧场,不妨设奶牛个数为k,只需要在所有有奶牛的点dfs后,统计有多少个点被遍历了k次就行了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1010,M = 10010;
int used[N];
int cnt[N];
int h[N],e[M],ne[M],idx;
int k,n,m;
bool st[N];
//从每头奶牛所在的点出发开始遍历,记录每个点被遍历了多少次,cnt[i]==k即是合格牧场
void add(int a,int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void dfs(int u)
{
cnt[u]++;
st[u] = true;
for(int i = h[u];i!=-1;i = ne[i])
{
int j = e[i];
if(!st[j])
{
dfs(j);
}
}
}
int main()
{
memset(h, -1, sizeof h);
cin>>k>>n>>m;
for(int i = 1;i<=k;i++)
{
cin>>used[i];
}
while (m -- ){
int a,b;
cin>>a>>b;
add(a,b);
}
for(int i = 1;i<=k;i++)
{
for(int j = 1;j<=n;j++)
{
st[j] = false;
}
dfs(used[i]);
}
int ans = 0;
for(int i = 1;i<=n;i++)
{
if(cnt[i]==k)
{
ans++;
}
}
cout<<ans;
return 0;
}
题目7:P4017 最大食物链计数
P4017
都说是拓扑排序的板子题/kk,但是自己还是借鉴了一下idea,不过ac的那一刻还是很开心的,注意n并不一定就是唯一的终点,所有出度为0的点都是终点,所以要开一个变量累加。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod = 80112002;
const ll N = 5010,M = 5e5+10;
ll h[N],e[M],ne[M],idx,n,m,q[N],f[N],d[N],dout[N];
bool st[N];
void add(ll a,ll b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void topsort()
{
ll hh = 0,tt = -1;
for(ll i = 1;i<=n;i++)
{
if(!d[i])
{
q[++tt] = i;//把入度为0的点入队
f[i] = 1;//初始化为1
}
}
while(hh<=tt)
{
ll t = q[hh++];//取出队头元素
for(ll i = h[t];i!=-1;i = ne[i])
{
ll j = e[i];
d[j]--;
f[j] = (f[j]%mod+f[t]%mod)%mod;
if(d[j]==0)
{
q[++tt] = j;
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
cin>>n>>m;
while (m -- ){
ll a,b;
cin>>a>>b;
add(a, b);
dout[a]++;
d[b]++;//入度++
}
topsort();
ll sum = 0;
for(int i = 1;i<=n;i++)
{
if(dout[i]==0)
(sum+=f[i])%=mod;
}
cout<<sum;
}
题目8:Acwing周赛36 第二题 4216. 图中的环
4216
n个点,m条边的无向图,判断该图是否是只有一个环的联通图。
做的时候比较呆,没有注意到一个小性质。
只有一个环意味着m==n,如果说没有环则
m
=
n
−
1
m=n-1
m=n−1
所以如果n!=m,那么直接输出“NO”,如果n=m直接图的dfs跑一遍,标记访问过的节点,最后记录节点个数看是否等于n即可.
AC code:
#include<bits/stdc++.h>
using namespace std;
const int N = 110,M = 110*110;
int h[N], e[M], ne[M], idx;
int n,m;
bool st[N],flag;
void add(int a, int b) // 添加一条边a->b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
if(st[u])
{
flag = true;
return ;
}
st[u] = true;
for(int i = h[u];i!=-1;i = ne[i])
{
int j = e[i];
if(!st[j])
dfs(j);
}
return ;
}
int main()
{
memset(h, -1, sizeof h);
memset(st, 0, sizeof st);
cin>>n>>m;
int t = m;
while(m--)
{
int a,b;
cin>>a>>b;
add(a, b);
add(b, a);
}
if(n==t)
{
dfs(1);
int cnt = 0;
for(int i = 1;i<=n;i++)
if(st[i]) cnt++;
if(cnt==n)
{
printf("YES");
return 0;
}
printf("NO");
}else{
printf("NO");
}
}
题目9: P1113 杂务
P1113
这道题和P4017 最大食物链计数很像,都需要注意出度为0的点可能为多个,所以要用一个变量去遍历一遍f数组得到最终的答案。有可能是和,有可能是max,根据题目问的不同而不同
做一遍拓扑排序即可,应注意不能bfs,要是简单的做一遍bfs,相当于当前任务的前置任务还没做完就开始做当前任务的后续任务了,这是不符题意的,必须要做拓扑排序的过程中记录状态的转移。
AC code:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll N = 1e6+10,M = 1e6+10;
ll a[N];
ll n;
ll h[N],e[M],ne[M],idx,q[N],f[N],d[N];
int hh,tt;
void add(ll a,ll b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void topsort(int hh,int tt)
{//直接bfs是不行的
while(hh<=tt)
{
auto t = q[hh++];
for(ll i = h[t];i!=-1;i = ne[i])
{
ll j = e[i];
d[j]--;
f[j] = max(f[j],a[j]+f[t]);
if(!d[j])
{
q[++tt] = j;
}
}
}
}
/*
错误的bfs code
void bfs(ll u)
{
ll hh = 0,tt = -1;
q[++tt] = u;
f[u] = a[u];
while(hh<=tt)
{
auto t = q[hh++];
for(ll i = h[t];i!=-1;i = ne[i])
{
ll j = e[i];
f[j] = max(f[j],f[t]+a[j]);
q[++tt] = j;
}
}
}
*/
int main()
{
memset(h, -1, sizeof h);
cin>>n;
ll t = 1;
ll tt = n;
while (tt -- ){
ll m;
cin>>m;
cin>>a[m];
ll x;
while(cin>>x&&x!=0)
{
add(x,m);
d[m]++;
}
t++;
}
hh = 0,tt = -1;
for(int i = 1;i<=n;i++)
{
if(d[i]==0)
{
f[i] = a[i];
q[++tt] = i;
}
}
topsort(hh,tt);
ll ans = -1e9;
for(int i = 1;i<=n;i++)
{
ans = max(ans,f[i]);
}
cout<<ans;
}