个人整理,大多数自己写的,少数来自网络,侵删
部分代码用到了C++11的新特性(如for-each循环,auto,结构体赋值)。目前ACM和PAT支持C++17,CCF CSP认证(大学生)支持C++11。但是NOIP和蓝桥杯 不支持C++11,使用模板时请注意。(4.23 Update: 第十四届蓝桥杯支持C++11了!!)
知识点待补充T_T
有向无环图(DAG)相关
拓扑排序
定义(百度百科):
在计算机科学领域,有向图的拓扑排序是对其顶点的一种线性排序,使得对于从顶点 u u u到顶点 v v v的每个有向边 u v uv uv, u u u在排序中都在 v v v之前。
在图论中,由一个有向无环图(DAG)的顶点组成的序列,当且仅当满足下列条件时,才能称为该图的一个拓扑排序(英语:Topological sorting):
1.序列中包含每个顶点,且每个顶点只出现一次;
2.若A在序列中排在B的前面,则在图中不存在从B到A的路径。
实现:BFS
复杂度: O ( n + e ) O(n+e) O(n+e)
(本笔记约定:n为图的点数,e为图的边数)
模板题:AcWing1191
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define debug(x) {cerr<<#x<<" = "<<(x)<<endl;}
const int N{
102};
int n, ru[N], chu[N];
vector<int>v[N];
vector<int>topo; //拓扑序列
void toposort()
{
queue<int>q;
for(int i=1;i<=n;i++) //让所有入度为0的点先入队
if(ru[i]==0)
q.push(i);
while(!q.empty())
{
int now=q.front(); q.pop();
topo.push_back(now);
for(auto nxt:v[now])
{
ru[nxt]--;
if(ru[nxt]==0) //入度减为0,则入队
q.push(nxt);
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++) //读入每个点的后继,以0结尾
{
int now; cin>>now;
while(now)
{
ru[now]++;
chu[i]++;
v[i].push_back(now);
cin>>now;
}
}
toposort();
for(auto i:topo)
cout<<i<<" ";
return 0;
}
Tarjan算法(有向图)
有向图的强连通分量(SCC)+缩点
在一个有向图中,求点权之和最大的路径
算法:先缩点,再做DP
复杂度: O ( n + e ) O(n+e) O(n+e)
//luogu P3387 缩点+DAG DP
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define debug(x) {cerr<<#x<<" = "<<(x)<<endl;}
const int N{
10005},M{
100005};
int n,m;
int score[M]; //点权
vector<int>v[N]; //原图
int score2[M]={
0}; //DAG点权
vector<int>dag[N]; //缩点后的DAG
int ru[N]={
0},chu[N]={
0}; //记录DAG中每个点的入度、出度
namespace Tarjan
{
stack<int>s;
bool inStack[N];
int dfn[N], low[N], id[N];
//dfn[i]是i的编号,low[i]表示i能到达的最小编号,id[i]是i属于的连通分量的编号
int timeStamp=0, scc=0; //时间戳,强连通分量编号
void tarjan(int u)
{
dfn[u] = low[u] = ++timeStamp;
s.push(u); inStack[u] = true;
for(auto nxt:v[u])
{
if(!dfn[nxt])
{
tarjan(nxt);
low[u] = min(low[u], low[nxt]);
}
else if(inStack[nxt])
low[u] = min(low[u], dfn[nxt]);
}
if(dfn[u]==low[u])
{
int now; scc++;
do {
now = s.top(); s.pop();
inStack[now] = false;
id[now] = scc;
} while(now!=u);
}
}//end tarjan
void suodian()
{
for(int i=1;i<=n;i++)
{
score2[id[i]] += score[i];
for(auto nxt:v[i])
if(id[i]!=id[nxt])
{
dag[id[i]].push_back(id[nxt]);
ru[id[nxt]]++; chu[id[i]]++;
}
}
} //缩点之后,id越小拓扑序越靠后
void work()
{
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
suodian();
} //根据id排了逆拓扑序
}
namespace DP
{
int f[N]={
0}; //f[i]代表以i为终点可获得的最大值
void work()
{
for(int i=Tarjan::scc;i>=1;i--)
{
f[i] += score2[i];
for(auto nxt:dag[i])
{
f[nxt] = max(f[nxt],f[i]);
}
}
int ans = 0;
for(int i=Tarjan::scc;i>=1;i--)
{
if(chu[i]==0)
ans = max(ans,f[i]);
}
cout<<ans;
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>score[i];
for(int i=1;i<=m;i++)
{
int x,y; cin>>x>>y;
v[x].push_back(y);
}
Tarjan::work();
DP::work();
return 0;
}
有向图的最大强连通子图
模板题:洛谷P1726
题意:求有向图中的一个最大点集,其中的点互相可以到达。如果有多个,输出字典序最小的。
解法:tarjan算法,记录每个强连通分量包含的点的个数,输出点的个数最多的集合即可。
#include <bits/stdc++.h>