一些图论模板
拓扑排序
一种基于 \(DAG\) 图的 \(O(n)\) 遍历图的算法,喜欢结合优先队列,反向建图进行考察。
tarjan强连通
stack<int>q;
void tarjan(int pos){
dfn[pos]=low[pos]=++cnt;//路径序号和所能到达的最小序号
bk[pos]=1;//标记:是否在栈中
q.push(pos);//入栈
for(int i=head[pos];i;i=e[i].last){
int to=e[i].to;
if(!dfn[to]){
tarjan(to);//1.向前走
low[pos]=min(low[pos],low[to]);//回溯更新
}
else if(bk[to])//不在栈中与我无关
low[pos]=min(low[pos],low[to]);//找到环起点
}
if(dfn[pos]==low[pos]){
col[pos]=++spct;//染色为超级点
bk[pos]=0;//出栈去标记
while(!q.empty() && q.top()!=pos){
int t1=q.top();q.pop();
col[t1]=spct;
bk[t1]=0;
}//出栈去标记
q.pop();//出栈去标记
}
return;
}
int main()
{
for(int i=1;i<=n;i++)
if(!dfn[i])tarjan(i);//不一定是连通图
return 0;
}
UPD on 2022.10.16
简单至上
void tarjan(int u){
dfn[u]=low[u]=++cnt;bk[u]=1;q.push(u);
for(int i=head[u];i;i=last[i]){
int v=to[i];
if(!dfn[v]){
tarjan(v);//1.向前走
low[u]=min(low[u],low[v]);//回溯更新
}
else if(bk[v])/*不在栈中与我无关*/low[u]=min(low[u],low[v]);//找到环起点
}
if(dfn[u]==low[u]){++spct;
while(q.top()!=u){
int now=q.top();q.pop();bk[now]=0;
col[now]=spct;
}//出栈去标记
bk[pos]=0;q.pop();//出栈去标记
col[u]=spct;//染色为超级点
}
return;
}
tarjan割点
void tarjan(int pos){
dfn[pos]=low[pos]=++cnt;//low为绕到的最早的点
int child=0;//树根的儿子数量
for(int i=head[pos];i;i=e[i].last){
int to=e[i].to;
if(!dfn[to]){
child++;
tarjan(to);
low[pos]=min(low[pos],low[to]);
if(low[to]>=dfn[pos] && root!=pos){
mark[pos]=1;//判断为割点
}
}
low[pos]=min(low[pos],dfn[to]);//必须用dfn
}
if(root==pos && child>=2)
mark[pos]=1;//判断为割点
return;
}
2-SAT
给出一个集合,集合中有 \(n\) 个 bool 型数。
给出 \(m\) 个关系,表示 \(p1\) 为 \(f1\) 或 \(p2\) 为 \(f2\) 必须满足一个。
那么对每个数值为1或0,分别建点,如果一个关系前者不满足,则后者一定满足。
所以可以建边 \((u,v)\) 表示当u成立时v一定成立。形成一张图后,对图进行缩点,如果一个数的两个值的点在同一个环中,则不成立,因为一个数只有一个值。
求一种可行方案,只要选每个数对应点中的scc中拓扑序大的值即可。
然后tarjan缩点后本来就是一个逆拓扑序的图。
n=read(),m=read();
for(int i=1;i<=m;i++){
p1=read(),f1=read(),p2=read(),f2=read();
add(p1+(f1^1)*n,p2+f2*n),add(p2+(f2^1)*n,p1+f1*n);
}
for(int i=1;i<=n*2;i++)if(!dfn[i])tarjan(i);
for(int i=1;i<=n;i++){
if(col[i]==col[i+n]){
printf("IMPOSSIBLE\n");
return 0;
}
}
printf("POSSIBLE\n");
for(int i=1;i<=n;i++){
printf("%d ",(col[i+n]<col[i]));
}
链式前向星
用链表存边(双向*2)
vector
用动态数组存点P能到的其他点
struct v{
int to,w;
};
vector<v>g[N];//N为点的数量
YCE说一个vector占20空间。
floyd
f[i][j]在枚举到k时的意思
在能绕第k个点是i到j的最短路
for(int k=1;k<=n;k++)//枚举绕第几个点
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=min(f[i][j],f[i][k]+f[k][j])
dfs
基于栈的一种搜索
bfs
基于队列的搜索
dijikstra
\(O(m \log_2^n)\)
m为边,n为点。
每次贪心查找离起点最近的点,将其能到的点加入优先队列
struct node{
int dis;int pos;
bool operator <(const node &x )const{
return x.dis<dis;//重载运算符
}
};
priority_queue<node>q;
void dijkstra()
{
while(!q.empty()){
node t1=q.top();
q.pop();
for(int i=head[t1.pos];i;i=e[i].last)
if(dis[e[i].to]>dis[t1.pos]+e[i].w){
dis[e[i].to]=dis[t1.pos]+e[i].w;
q.push((node){dis[e[i].to],e[i].to});
}
}
return;
}
int main()
{
for(int i=1;i<=n;i++)
dis[i]=0x3f3f3f3f;
dis[s]=0;
q.push((node){0,s});
dijkstra();
return 0;
}
spfa
\(O(km)\)
k为每个点平均进队次数,m为边数。
像bfs那样广度拓展
void spfa(){
while(!q.empty()){
int t1=q.front();
q.pop();
for(int i=head[t1];i;i=e[i].last)
if(dis[e[i].to]>dis[t1.pos]+e[i].w){
dis[e[i].to]=dis[t1.pos]+e[i].w;
q.push(e[i].to);
}
}
return 0;
}
双端队列优化
其实是一种贪心优化
把距离近的点(比队首)先跑
void spfa(){
while(!q.empty()){
ll t=q.front();
q.pop_front();
for(ll i=head[t];i;i=e[i].last){
if(dis[e[i].to]>dis[t]+e[i].w){
dis[e[i].to]=dis[t]+e[i].w;
if(!q.empty() && dis[e[i].to<dis[q.front()])q.push_front(e[i].to);
else q.push_back(e[i].to);
}
}
}
return;
}
spfa判负环
- 为什么打标记,因为不打标记会导致一些已经进入队列的点重复进入,不会影响最短路的答案(效率也没什么影响),但对进队次数的统计会影响。
- 是进队次数\(\ge n\)。
void spfa(){
queue<int>q;
memset(dis,0x3f,sizeof(dis));
vis[1]=1;q.push(1);cnt[1]++;dis[1]=0;
while(!q.empty()){
int u=q.front();q.pop();vis[u]=0;
for(int i=head[u];i;i=last[i]){int v=to[i];
if(dis[v]>dis[u]+w[i]){
dis[v]=dis[u]+w[i];
if(!vis[v]){
vis[v]=1;q.push(v);cnt[v]++;
if(cnt[v]>=n){can=1;return;}
}
}
}
}
return;
}
spfa 搜索判负环
UPD on 2022.10.19 别看了,这可能是错的。
void spfa(int t1){
cnt[t1]++;
if(cnt[t1]>=n){
flag=1;
return;
}
for(int i=head[t1];i;i=last[i]){
if(dis[to[i]]>dis[t1]+w[i]){
dis[to[i]]=dis[t1]+w[i];
spfa(to[i]);
}
}
return;
}
此外,在做P3199 [HNOI2009]最小圈的时候,发现了一种新奇的求负环方法(经过我的反复测试,它确实快,但洛谷模板有个点死活过不去)。目前认为它只能求全图负环(但也不会死循环吧)。总之慎用。
void dfs(int u,double key){
if(can)return;
vis[u]++;
for(int i=head[u];i;i=last[i]){int v=to[i];
if(dis[v]>dis[u]+w[i]){
dis[v]=dis[u]+w[i];
if(vis[v]){
can=1;
return;
}
dfs(v,key);
}
}
vis[u]=0;
}
int main(){
can=0;memset(vis,0,sizeof(vis));for(int i=1;i<=n;i++)dis[i]=0;
for(int s=1;s<=n;s++){
dfs(s,key);
}
return 0;
}
最小生成树(Kruskal)
贪心的找最小的边加入队列
适合边少的图
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=m;i++)
{
if(check(e[i].v)!=check(e[i].m))
{
tot++;
ans+=e[i].w;
f[check(e[i].v)]=check(e[i].m);
}
if(tot==n-1)break;
}
(Prim)
每次把目前能到的点中的合法最小边选进来
然后再把边终点的其他边加入集合
适合边多的图
void prim(){
ans=0;
bk[1]=1;
for(int i=2;i<=n;i++)
lowcost[i]=dis[1][i],bk[i]=0;
for(int i=2;i<=n;i++){
int min=INT_MAX,id;
for(int j=1;j<=n;j++)
if(!bk[j] && lowcost[j]<min){
min=lowcost[j];
id=j;
}
ans+=min;
bk[id]=1;
for(int j=1;j<=n;j++)
if(!bk[j] && lowcost[j]>dis[id][j])
lowcost[j]=dis[id][j];
}
return;
}
并查集
联通性的判断
int check(int x)
{
if(a[x]==x)return x;
return a[x]=check(a[x]);
}
按秩合并
也就是可持久化并查集中常用的合并方式!其实也就是一种类似于启发式合并的方式,每一次合并时选择一个深度小的点向深度大的合并。
可持久化并查集
用按秩合并,用可持久化线段树分别维护fa数组和dep数组即可
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+110;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0' && c<='9'){
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
struct node{
int rson,lson,l,r,fa;
}e[N<<5];
struct DP{
int rson,lson,l,r,dep;
}h[N<<5];
int n,m,cnt,cnt2,root[N],root2[N];
void buildFA(int &u,int l,int r){
u=++cnt;
e[u].l=l;
e[u].r=r;
if(l==r){
e[u].fa=l;
return;
}
int mid=(l+r)>>1;
buildFA(e[u].lson,l,mid);
buildFA(e[u].rson,mid+1,r);
return;
}
void buildDEP(int &u,int l,int r){
u=++cnt2;
h[u].l=l;
h[u].r=r;
if(l==r){
h[u].dep=1;
return;
}
int mid=(l+r)>>1;
buildDEP(h[u].lson,l,mid);
buildDEP(h[u].rson,mid+1,r);
return;
}
void updateFA(int pre,int &u,int x,int va){
if(!u){
u=++cnt;
e[u].l=e[pre].l;e[u].r=e[pre].r;
}
if(e[pre].l==e[pre].r){
e[u].fa=va;
return;
}
int mid=(e[u].l+e[u].r)>>1;
if(x<=mid){
e[u].rson=e[pre].rson;
updateFA(e[pre].lson,e[u].lson,x,va);
}
else{
e[u].lson=e[pre].lson;
updateFA(e[pre].rson,e[u].rson,x,va);
}
return;
}
void updateDEP(int pre,int &u,int x,int va){
if(!u){
u=++cnt2;
h[u].l=h[pre].l;h[u].r=h[pre].r;
}
if(h[pre].l==h[pre].r){
h[u].dep=va;
return;
}
int mid=(h[u].l+h[u].r)>>1;
if(x<=mid){
h[u].rson=h[pre].rson;
updateDEP(h[pre].lson,h[u].lson,x,va);
}
else{
h[u].lson=h[pre].lson;
updateDEP(h[pre].rson,h[u].rson,x,va);
}
return;
}
int FA(int u,int x){
if(e[u].l==e[u].r)return e[u].fa;
int mid=(e[u].l+e[u].r)>>1;
if(x<=mid)return FA(e[u].lson,x);
else return FA(e[u].rson,x);
}
int check(int x,int time){
int _fa=FA(root[time],x);
if(x==_fa)return x;
else return check(_fa,time);
}
int DEP(int u,int x){
if(h[u].l==h[u].r)return h[u].dep;
int mid=(h[u].l+h[u].r)>>1;
if(x<=mid)return DEP(h[u].lson,x);
else return DEP(h[u].rson,x);
}
int main(){
n=read(),m=read();
int opt,a,b;
buildFA(root[0],1,n);
buildDEP(root2[0],1,n);
for(int i=1;i<=m;i++){
opt=read();
if(opt==1){
a=read(),b=read();
int A=check(a,i-1);
int B=check(b,i-1);
int depA=DEP(root2[i-1],A);
int depB=DEP(root2[i-1],B);
if(depA>=depB){
updateDEP(root2[i-1],root2[i],A,max(depA,depB+1));
updateFA(root[i-1],root[i],B,A);
}
else{
updateDEP(root2[i-1],root2[i],B,max(depB,depA+1));
updateFA(root[i-1],root[i],A,B);
}
}
if(opt==2){
a=read();
root[i]=root[a],root2[i]=root2[a];
}
if(opt==3){
a=read(),b=read();
int A=check(a,i-1);
int B=check(b,i-1);
if(A==B)printf("1\n");
else printf("0\n");
root[i]=root[i-1];
root2[i]=root2[i-1];
}
}
return 0;
}

浙公网安备 33010602011771号