以下部分内容抄自神仙博客
最短路计数
要记录 v i s vis vis数组避免算重。
if(dis[v]>dis[u]+W[i]) dis[v]=dis[u]+W[i],num[v]=num[u],Q.push(mp(-dis[v],v));
else if(dis[v]==dis[u]+W[i]) num[v]+=num[u];
这里必须加
e
l
s
e
else
else,因为上面那个算完之后下面那个就成立了。
模板
#include<bits/stdc++.h>
#define pii std::pair<int,int>
#define mp std::make_pair
#define fi first
#define se second
#define re register
#define cs const
cs int N=2e3+10,oo=0x3f3f3f3f;
namespace IO{
cs int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
template <typename T>
inline T get(){
char ch=gc();T x=0;
while(!isdigit(ch)) ch=gc();
while(isdigit(ch)) x=(x+(x<<2)<<1)+(ch^48),ch=gc();
return x;
}
inline int gi(){return get<int>();}
}
using IO::gi;
int n,m,len[N][N],dis[N],num[N],vis[N];
int Next[N*N/2],V[N*N/2],W[N*N/2],Head[N],cnt=0;
inline void add(int u,int v,int w){Next[++cnt]=Head[u],V[cnt]=v,W[cnt]=w,Head[u]=cnt;}
inline void dijkstra(int S){
std::priority_queue<pii> Q;
memset(dis,0x3f,sizeof dis);
dis[S]=0,num[S]=1,Q.push(mp(0,S));
while(!Q.empty()){
int u=Q.top().se;Q.pop();
if(vis[u]) continue;vis[u]=1;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
if(dis[v]>dis[u]+W[i]) dis[v]=dis[u]+W[i],num[v]=num[u],Q.push(mp(-dis[v],v));
else if(dis[v]==dis[u]+W[i]) num[v]+=num[u];
}
}
}
int main(){
// freopen("1608.in","r",stdin);
n=gi(),m=gi(),memset(len,0x3f,sizeof len);
while(m--){
int u=gi(),v=gi(),w=gi();
if(len[u][v]>w) len[u][v]=w;
}
for(int re i=1;i<=n;++i)
for(int re j=1;j<=n;++j)
if(len[i][j]!=oo) add(i,j,len[i][j]);
dijkstra(1),printf((dis[n]==oo)?"No answer":"%d %d\n",dis[n],num[n]);
}
SPFA判负环
若一个点进出队列次数超过 n n n次,则有负环。传送门
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
cs int N=2001,M=6001;
namespace IO{
cs int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
template<typename T>
inline T get(){
char ch=gc();T x=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=0;ch=gc();}
while(isdigit(ch)) x=(x+(x<<2)<<1)+(ch^48),ch=gc();
return f?x:-x;
}
inline int gi(){return get<int>();}
inline ll gl(){return get<ll>();}
}
using IO::gi;
using IO::gl;
int T,n,m,dis[N],vis[N],num[N];
int Next[M],V[M],W[M],Head[N],cnt=0;
inline void add(int u,int v,int w){Next[++cnt]=Head[u],V[cnt]=v,W[cnt]=w,Head[u]=cnt;}
inline void init(){
memset(Head,0,sizeof Head),cnt=0;
}
inline bool SPFA(int S){
std::queue<int> Q;
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
Q.push(S),vis[S]=1,dis[S]=0,num[S]=1;
while(!Q.empty()){
int u=Q.front();Q.pop(),vis[u]=0;
for(int i=Head[u],v=V[i];i;v=V[i=Next[i]])
if(dis[v]>dis[u]+W[i]){
dis[v]=dis[u]+W[i],num[v]=num[u]+1;
if(num[v]>n) return true;
if(!vis[v]) vis[v]=1,Q.push(v);
}
}return false;
}
int main(){
// freopen("3385.in","r",stdin);
T=gi();
while(T--){
n=gi(),m=gi(),init();
while(m--){
int u=gi(),v=gi(),w=gi();
(w<0)?(add(u,v,w)):(add(u,v,w),add(v,u,w));
}puts((SPFA(1))?("YE5"):("N0"));
}
}
割点
根节点为割点条件:
d
f
s
dfs
dfs树上的儿子数大于1
非根节点为割点条件:
l
o
w
[
v
]
⩾
d
f
n
[
u
]
low[v] \geqslant dfn[u]
low[v]⩾dfn[u],即存在一个儿子不能
d
f
s
dfs
dfs到当前点的祖先节点。
注意
d
f
s
dfs
dfs开始的时候要把
r
o
o
t
root
root置为开始
d
f
s
dfs
dfs的节点。
一道例题:
[HNOI2012]矿场搭建
这个题就是考虑若干个点双集合。
若点双里包含割点个数大等于2,则不用安排。因为只有一个点会爆掉,所以这个点双至少可以和一个其他点双连通。
若点双里只有一个割点,那么出口可以安排在非割点的任意地方。你会发现不管炸割点或者炸出口都可以安排所有人逃离。
若整个点双无割点,则至少安排两个,因为如果炸掉了一个出口,就跑不掉了。
于是求出割点后
d
f
s
dfs
dfs一遍找出每个点双对应的答案。
第一个答案就是加法原理,第二个就是乘法原理。
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
cs int N=505,M=505;
namespace IO{
cs int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
template<typename T>inline T get(){
char ch=gc();T x=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=0;ch=gc();}
while(isdigit(ch)) x=(x+(x<<2)<<1)+(ch^48),ch=gc();
return f?x:-x;
}
inline int gi(){return get<int>();}
inline ll gl(){return get<ll>();}
}
using IO::gi;
using IO::gl;
inline int min(int x,int y){return (x<y)?x:y;}
inline int max(int x,int y){return (x>y)?x:y;}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,m,u,v,num_cut,num_pnt,num,Case=0;
int Head[N],Next[M<<1],V[M<<1],cnt;
int col[N],dfn[N],low[N],cut[N],root,tot;
int ans1;ll ans2;
inline void init(){
n=cnt=tot=num=ans1=0;ans2=1;
memset(Head,0,sizeof Head);
memset(dfn,0,sizeof dfn);
}
inline void add(int u,int v){Next[++cnt]=Head[u],V[cnt]=v,Head[u]=cnt;}
inline void tarjan(int u,int son=0){
cut[u]=col[u]=0,dfn[u]=low[u]=++tot;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
if(!dfn[v]){
++son,tarjan(v),Min(low[u],low[v]);
cut[u]|=(u==root&&son>1)|(u!=root&&low[v]>=dfn[u]);
}else Min(low[u],dfn[v]);
}
}
inline void dfs(int u){
col[u]=num;if(cut[u]) return;
++num_pnt;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
if(cut[v]&&col[v]!=num) ++num_cut,col[v]=num;
if(!col[v]) dfs(v);
}
}
int main(){
// freopen("3225.in","r",stdin);
while(m=gi()){
init();
while(m--) u=gi(),v=gi(),Max(n,max(u,v)),add(u,v),add(v,u);
for(int re i=1;i<=n;++i) if(!dfn[i]) tarjan(root=i);
for(int re i=1;i<=n;++i) if(!(col[i]+cut[i])){
++num,num_pnt=num_cut=0,dfs(i);
if(num_cut==1) ans1+=1,ans2*=num_pnt;
if(num_cut==0) ans1+=2,ans2*=1ll*num_pnt*(num_pnt-1)/2;
}printf("Case %d: %d %lld\n",++Case,ans1,ans2);
}
}
附上点双写法:
注意要先求割点再跑点双,因为要记录点双里有几个割点。
这里使用的是存边的方法。
然而有一个问题:当
d
f
s
dfs
dfs到一条非树边时,要判断一下
d
f
n
dfn
dfn值大小关系。如果
d
f
n
[
v
]
>
d
f
n
[
u
]
dfn[v]>dfn[u]
dfn[v]>dfn[u],就说明它是割顶连向子树的一条边。此时以
u
u
u为割顶的点双已求出,而如果再把这条边压进栈里,这条边就弹不出去了,它的两个端点就会被计入其他点双。于是就挂了。然后判一个
d
f
n
[
v
]
<
d
f
n
[
u
]
dfn[v]<dfn[u]
dfn[v]<dfn[u]就可以过了。也就是说这条边是一条返祖边。
对于这道题而言,贡献与边无关,所以其实并不用记录点双中具体的边的情况,只要保证边能更新到所有点即可,所以返祖边不记也是可以的。
但是对于需要统计边的情况的题,就必须要记录返祖边了。
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
cs int N=505,M=505;
namespace IO{
cs int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
template<typename T>inline T get(){
char ch=gc();T x=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=0;ch=gc();}
while(isdigit(ch)) x=(x+(x<<2)<<1)+(ch^48),ch=gc();
return f?x:-x;
}
inline int gi(){return get<int>();}
inline ll gl(){return get<ll>();}
}
using IO::gi;
using IO::gl;
inline int max(int x,int y){return (x>y)?x:y;}
inline int min(int x,int y){return (x<y)?x:y;}
inline void Min(int &x,int y){if(x>y)x=y;}
inline void Max(int &x,int y){if(x<y)x=y;}
int n,m,u,v,Case=0,ans1;ll ans2;
int Head[N],Next[M<<1],V[M<<1],F[M<<1],cnt=1;
int dfn[N],low[N],cut[N],belong[N],st[M<<1],top,root,tot;
int bcc_pnt[N],bcc_cut[N],bcc_cnt;
inline void add(int u,int v){Next[++cnt]=Head[u],V[cnt]=v,F[cnt]=u,Head[u]=cnt;}
inline void init(){
ans1=0,ans2=1;
n=tot=cnt=bcc_cnt=top=0;
memset(Head,0,sizeof Head);
memset(dfn,0,sizeof dfn);
memset(cut,0,sizeof cut);
memset(bcc_pnt,0,sizeof bcc_pnt);
memset(bcc_cut,0,sizeof bcc_cut);
memset(belong,0,sizeof belong);
}
inline void clear(){memset(dfn,0,sizeof dfn),tot=0;}
inline void tarjan_cut(int u,int son=0){
dfn[u]=low[u]=++tot;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
if(!dfn[v]) ++son,tarjan_cut(v),Min(low[u],low[v]),cut[u]|=(u==root&&son>1)|(u!=root&&low[v]>=dfn[u]);
else Min(low[u],dfn[v]);
}
}
inline void tarjan_bcc(int u){
dfn[u]=low[u]=++tot;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
if(!dfn[v]){
st[++top]=i,tarjan_bcc(v),Min(low[u],low[v]);
if(low[v]>=dfn[u]){
++bcc_cnt;
while(1){
int now=st[top--];
if(belong[F[now]]!=bcc_cnt) bcc_pnt[bcc_cnt]+=1,bcc_cut[bcc_cnt]+=cut[F[now]],belong[F[now]]=bcc_cnt;
if(belong[V[now]]!=bcc_cnt) bcc_pnt[bcc_cnt]+=1,bcc_cut[bcc_cnt]+=cut[V[now]],belong[V[now]]=bcc_cnt;
if(now==i) break;
}
}
}else{
Min(low[u],dfn[v]);
if(dfn[V[i]]<dfn[F[i]]) st[++top]=i;
}
}
}
int main(){
// freopen("bzoj2730.in","r",stdin);
while(m=gi()){
init();
while(m--) Max(n,u=gi()),Max(n,v=gi()),add(u,v),add(v,u);
for(int re i=1;i<=n;++i) if(!dfn[i]) tarjan_cut(root=i); clear();
for(int re i=1;i<=n;++i) if(!dfn[i]) tarjan_bcc(i);
for(int re i=1;i<=bcc_cnt;++i){
if(bcc_cut[i]==1) ans1+=1,ans2*=bcc_pnt[i]-1;
if(bcc_cut[i]==0) ans1+=2,ans2*=1ll*bcc_pnt[i]*(bcc_pnt[i]-1)/2;
}printf("Case %d: %d %lld\n",++Case,ans1,ans2);
}
}
割边
一条边连接
(
u
,
v
)
(u,v)
(u,v),那么它是割边的条件就是
l
o
w
[
v
]
>
d
f
n
[
u
]
low[v]>dfn[u]
low[v]>dfn[u],注意是严格大于。
所以当前节点的
l
o
w
low
low值不能用
f
a
fa
fa更新。然而有一个问题,就是当重边存在的时候,是可以用
f
a
fa
fa更新的,因为可以从重边中的一条进入
f
a
fa
fa,所以记
f
a
fa
fa是不靠谱的。考虑记录入边的编号,那么只需要保证当前边不是入边的反向边就能解决上述问题了。这个东西可以利用加边的方式,用异或来表示一组双向边。注意这样的话
c
n
t
cnt
cnt要初始化为1。
模板
其实这个题也含有割点模板。
#include<bits/stdc++.h>
#define pii std::pair<int,int>
#define mp std::make_pair
#define ll long long
#define pb push_back
#define re register
#define se second
#define cs const
#define fi first
cs int N=2e4+10,M=1e5+10;
namespace IO{
cs int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
template<typename T>inline T get(){
char ch=gc();T x=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=0;ch=gc();}
while(isdigit(ch)) x=(x+(x<<2)<<1)+(ch^48),ch=gc();
return f?x:-x;
}
inline int gi(){return get<int>();}
inline ll gl(){return get<ll>();}
}
using IO::gi;
using IO::gl;
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
inline int max(int x,int y){return (x>y)?x:y;}
inline int min(int x,int y){return (x<y)?x:y;}
int n,m,u,v;
int Head[N],Next[M<<1],V[M<<1],cnt=1;
int dfn[N],low[N],bridge[M<<1],cut[N],root,tot=0;
inline void add(int u,int v){Next[++cnt]=Head[u],V[cnt]=v,Head[u]=cnt;}
inline void clear(){memset(dfn,0,sizeof dfn),tot=0;}
inline void tarjan_bridge(int u,int in_edge){
dfn[u]=low[u]=++tot;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
if(!dfn[v]){
tarjan_bridge(v,i),Min(low[u],low[v]);
if(low[v]>dfn[u]) bridge[i]=bridge[i^1]=1;
}else if(i!=(in_edge^1)) Min(low[u],dfn[v]);
}
}
inline void tarjan_cut(int u,int son=0){
dfn[u]=low[u]=++tot;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
if(!dfn[v]){
++son,tarjan_cut(v),Min(low[u],low[v]);
cut[u]|=(u==root&&son>1)|(u!=root&&low[v]>=dfn[u]);
}else Min(low[u],dfn[v]);
}
}
std::vector<int> pnt;
std::vector<pii> edge;
int main(){
// freopen("Hiho1183.in","r",stdin);
n=gi(),m=gi();
while(m--) u=gi(),v=gi(),add(u,v),add(v,u);
for(int re i=1;i<=n;++i) if(!dfn[i]) tarjan_bridge(i,0);
clear();
for(int re i=1;i<=n;++i) if(!dfn[i]) tarjan_cut(root=i,0);
for(int re i=1;i<=n;++i) if(cut[i]) pnt.pb(i);
for(int re i=2;i< cnt;i+=2) if(bridge[i])
edge.pb((V[i]<V[i^1])?mp(V[i],V[i^1]):mp(V[i^1],V[i]));
std::sort(edge.begin(),edge.end());
if(pnt.empty()) printf("Null");
else for(int re i=0;i<pnt.size();++i) printf("%d ",pnt[i]);
puts("");
for(int re i=0;i<edge.size();++i)
printf("%d %d\n",edge[i].fi,edge[i].se);
}
点双
见上方[矿场搭建]
边双
和强连通分量有些类似,只需要记录一下入边,保证当前边不是入边的反向边即可。
模板
#include<bits/stdc++.h>
#define pii std::pair<int,int>
#define mp std::make_pair
#define ll long long
#define re register
#define se second
#define cs const
#define fi first
cs int N=2e4+10,M=1e5+10;
namespace IO{
cs int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
template <typename T>
inline T get(){
char ch=gc();T f=1,x=0;
while(!isdigit(ch)){if(ch=='-')f=0;ch=gc();}
while(isdigit(ch)) x=(x+(x<<2)<<1)+(ch^48),ch=gc();
return x;
}
inline int gi(){return get<int>();}
inline ll gl(){return get<ll>();}
}
using IO::gi;
using IO::gl;
template<typename T>inline void Min(T &x,T y){if(x>y)x=y;}
template<typename T>inline void Max(T &x,T y){if(x<y)x=y;}
template<typename T>inline T max(T x,T y){return x>y?x:y;}
template<typename T>inline T min(T x,T y){return x<y?x:y;}
int n,m,u,v;
int Head[N],Next[M<<1],V[M<<1],cnt=1;
int dfn[N],low[N],st[N],top=0,tot=0;
int ebc_id[N],belong[N],ebc_cnt=0;
inline void add(int u,int v){Next[++cnt]=Head[u],V[cnt]=v,Head[u]=cnt;}
inline void tarjan(int u,int in_edge){
dfn[u]=low[u]=++tot,st[++top]=u;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]]){
if(!dfn[v]) tarjan(v,i),Min(low[u],low[v]);
else if(i!=(in_edge^1)) Min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
++ebc_cnt;
while(1){
int now=st[top--];
belong[now]=ebc_cnt;
Min(ebc_id[ebc_cnt],now);
if(now==u) break;
}
}
}
int main(){
// freopen("Hihocoder1184.in","r",stdin);
n=gi(),m=gi(),memset(ebc_id,0x3f,sizeof ebc_id);
while(m--) u=gi(),v=gi(),add(u,v),add(v,u);
for(int re i=1;i<=n;++i) if(!dfn[i]) tarjan(i,0);
printf("%d\n",ebc_cnt);
for(int re i=1;i<=n;++i)
printf("%d ",ebc_id[belong[i]]);
}
例题:[POJ3352]Road Construction
求:在一个无向连通图中至少加入多少条边使得不存在割边。
做法:求出边双之后缩点,这会形成一棵树,令叶子节点个数为
l
e
a
f
_
c
n
t
leaf\_cnt
leaf_cnt,答案即为:
(
l
e
a
f
_
c
n
t
+
1
)
/
2
(leaf\_cnt+1)/2
(leaf_cnt+1)/2。
大概可以理解为在叶子节点之间两两配对。
重复描述一条边不是重边而是同一条边的重复说明
爷是真滴佛辣。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<map>
#include<algorithm>
#define pii std::pair<int,int>
#define mp std::make_pair
#define ll long long
#define re register
#define cs const
cs int N=5e4+10,M=5e4+10;
namespace IO{
cs int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;}
template<typename T>
inline T get(){
char ch=gc();T f=1,x=0;
while(!isdigit(ch)){if(ch=='-')f=0;ch=gc();}
while(isdigit(ch)) x=(x+(x<<2)<<1)+(ch^48),ch=gc();
return f?x:-x;
}
inline int gi(){return get<int>();}
inline ll gl(){return get<ll>();}
}
using IO::gi;
using IO::gl;
inline void Min(int &x,int y){if(x>y)x=y;}
inline void Max(int &x,int y){if(x<y)x=y;}
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
int n,m,u,v;
int Head[N],Next[M<<1],V[M<<1],in[N],cnt=1,leaf_cnt=0;
int dfn[N],low[N],belong[N],st[N],top=0,tot=0,ebc_cnt=0;
inline void add(int u,int v){Next[++cnt]=Head[u],V[cnt]=v,Head[u]=cnt;}
inline void tarjan(int u,int in_edge){
dfn[u]=low[u]=++tot,st[++top]=u;
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]])
if(!dfn[v]) tarjan(v,i),Min(low[u],low[v]);
else if(i^in_edge^1) Min(low[u],low[v]);
if(low[u]==dfn[u]){
++ebc_cnt;
while(1){
int now=st[top--];
belong[now]=ebc_cnt;
if(now==u) break;
}
}
}
std::map<pii,int> Link;
int main(){
//freopen("1094.in","r",stdin);
n=gi(),m=gi();
while(m--){
u=gi(),v=gi();
if(!Link[mp(u,v)]) add(u,v),add(v,u),Link[mp(u,v)]=Link[mp(v,u)]=1;
}
tarjan(1,0);
for(int re u=1;u<=n;++u)
for(int re i=Head[u],v=V[i];i;v=V[i=Next[i]])
in[belong[v]]+=(belong[u]!=belong[v]);
if(ebc_cnt==1) return puts("0"),0;
for(int re i=1;i<=ebc_cnt;++i) leaf_cnt+=(in[i]==1);
printf("%d\n",(leaf_cnt+1)>>1);
}