最短路
dijkstra
基於貪心思想,不能處理負邊權(每個點第一次出爲最短路,但負邊權若離源點較遠,還沒更新到就已確定了答案)
inline void dijkstra(){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[1]=0;
priority_queue<pair<int,int>>q;
q.push(make_pair(0,1));
while(!q.empty()){
int x-q.top();q.pop();
if(vis[x])continue;
vis[x]=1;
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].v;
if(dis[v]>dis[x]+e[i].w){
dis[v]=dis[x]+e[i].w;
q.push(make_pair(-dis[v],v));
}
}
}
}
SPFA
複雜度玄學,最壞
判負環:表示從1到的最短路包含的邊數,,若發現則有負環(也可以判一個點入隊大於等於次,但複雜度低些)
別用雙端隊列優化,卡的更兇,且無法判負環(入隊次也不一定是被更新了次),版也會被卡
queue<int>q;
inline bool spfa(){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
q.push(1);
vis[1]=1;dis[1]=0;
while(!q.empty()){
int x=q.front();q.pop();
vis[x]=0;
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].v;
if(dis[v]>dis[x]+e[i].w){
dis[v]=dis[x]+e[i].w;
cou[v]=cou[x]+1;//!!
if(cou[v]>=n)return 1;
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
return 0;
}
floyd
任意兩點之間最短路,
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
求傳遞閉包
關係具有傳遞性,且傳遞性推導出儘量多的元素之間的關係,如判有向圖,一個點是否能到達另一個點
普通版
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
b[i][j]|=b[i][k]&b[k][j];
版
bitset<N>b[N];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(f[j].test(i))f[j]|=f[i];//用f[i]更新f[j]
最小環:
在中再開一個更新答案
vector<int>path;
inline void get_path(int x,int y){
if(pox[x][y]==0)return;
get_path(x,pos[x][y]);
path.push_back(pos[x][y]);
get_path(pos[x][y],y);
}
for(int k=1;k<=n;k++){
for(int i=1;i<k;i++)
for(int j=i+1;j<k;j++)
if(dis[i][j]+e[j][k]+a[k][i]<ans){
ans=dis[i][j]+a[j][k]+a[k][i];
path.clear();
path.push_back(i);
get_path(i,j);
path.push_back(j);
path.push_back(k);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dis[i][j]>dis[i][k]+dis[k][j]){
dis[i][j]=dis[i][k]+dis[k][j];
pos[i][j]=k;
}
}
也可以設求完之後就是最小環了
最小生成樹
kruskal
貪心加入最小的邊
inline void find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1,fu,fv;i<=m;i++){
fu=find(e[i].u);fv=find(e[i].v);
if(fu==fv)continue;
fa[fu]=fv;
ans+=e[i].w;
}
prim
適用於稠密圖
inline void prim(){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[1]=0;
for(int i=1,x;i<=n;i++){
x=0;
for(int j=1;j<=n;j++)
if(!vis[j]&&(!x||dis[j]<dis[x]))x=j;
vis[j]=1;
for(int j=1;j<=n;j++)
if(!vis[j])dis[j]=min(dis[j],e[x][j]);
}
}
for(int i=1;i<=n;i++)ans+=dis[i];
最小樹形圖
方法:(朱-劉算法)
-
求最短弧集合E
-
判斷集合E中有沒有有向環,如果有轉步驟3,否則轉4
-
收縮點,把有向環收縮成一個點,並且對圖重新構建,包括邊權值的改變和點的處理,之後再轉步驟1
-
展開收縮點,求得最小樹形圖
while(1){
memset(mn,0x3f,sizeof(mn));
for(int i=1,u,v,w;i<=m;i++){
u=e[i].u;v=e[i].v;w=e[i].w;
if(u!=v&&w<mn[v]){
mn[v]=w;from[v]=u;
}
}
for(int i=1;i<=n;i++)
if(i!=rt&&mn[i]==inf){
printf("-1");return (0-0);
}
cnt=0;
memset(vis,0,sizeof(vis));
memset(cn,0,sizeof(cn));
for(int i=1,v;i<=n;i++){
if(i==rt)continue;
ans+=mn[i];
v=i;
while(vis[v]!=i&&!cn[v]&&v!=rt){
vis[v]=i;
v=from[v];
}
if(!cn[v]&&v!=rt){
cn[v]=++cnt;
for(int j=from[v];j!=v;j=from[j])
cn[j]=cnt;
}
}
if(!cnt)break;
for(int i=1;i<=n;i++)
if(!cn[i])cn[i]=++cnt;
for(int i=1,u,v;i<=m;i++){
u=e[i].u;v=e[i].v;
e[i].u=cn[u];e[i].v=cn[v];
if(cn[u]!=cn[v])e[i].w-=mn[v];//代替之前的邊
}
rt=cn[rt];
n=cnt;
}
printf("%d",ans);
樹的直徑
- 兩遍求出最遠點(負邊權不適用)
- dp求,當前點到葉子節點的最大路徑
(樹的直徑可以作爲很多樹上問題的突破口)
inline void dp(int x,int fa){
for(int i=first[x];i;i=e[i].nxt){
v=e[i].v;
if(v==fa)continue;
dp(v,x);
ans=max(ans,dis[x]+dis[v]+e[i].w);
dis[x]=max(dis[x],dis[v]+e[i].w);
}
if(x==rt)ans=max(ans,dis[x]);
}
最近公共祖先(LCA)
倍增
inline void lca_pre(int x){
for(int i=1;(1<<i)<=dep[x];i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].v;
if(dep[v]||v==fa[x][0])continue;
dep[v]=dep[x]+1;
fa[v][0]=x;
lca_pre(v);
}
}
inline int LCA(int u,int v){
if(dep[u]<dep[v])swap(u,v);
int r=dep[u]-dep[v];
for(int i=0;(1<<i)<=r;i++)
if(r&(1<<i))u=fa[u][i];
if(u==v)return u;
for(int i=19;i>=0;i--)
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];v=fa[v][i];
}
return fa[u][0];
}
樹剖
inline void dfs1(int x){
dis[x]=dis[fa[x]]+num[x];
siz[x]=1;//!!!!!
for(re int i=0,v;i<ne[x].size();i++){
v=ne[x][i];
if(v==fa[x])continue;
dep[v]=dep[x]+1;
fa[v]=x;
dfs1(v);
siz[x]+=siz[v];
if(siz[v]>siz[son[x]])son[x]=v;
}
}
inline void dfs2(int x,int tp){
dfn[x]=++tot;
top[x]=tp;
if(son[x])dfs2(son[x],tp);
for(re int i=0,v;i<ne[x].size();i++){
v=ne[x][i];
if(v==fa[x]||v==son[x])continue;
dfs2(v,v);
}
}
inline int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])u^=v^=u^=v;
u=fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
tarjan
離線算法
把詢問存儲至每個節點處,若回溯完成則將節點指向父節點,即爲所指向的節點
inline int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline void tarjan(int x){
vis[x]=1;
for(int i=first[x],v;i;i=e[i].nxt;i++){
v=e[i].v;
if(vis[v])continue;
tarjan(v);
fa[v]=x;
}
for(int i=0,v,id;i<q[x].size();i++){
v=q[x][i].first;id=q[x][i].second;
iF(vis[v]==2)lca[id]=find(v);
}
vis[x]=2;
}
基環樹
- 把環看做一個點
- 環形處理方法:破環爲鏈(擴兩倍),強制環上兩點的關係
差分約數
約數條件:變形與極其相似,可以從向連一條的邊(若約束過於分散可以加入0號節點)跑最短路即可(注意負環)
若可以求最長路
tarjan與無向圖連通性
在圖論的連通性問題中,我們經常要從搜索樹的角度來分析
有時需要可以建”補圖“
割邊
inline void tarjan(int x,int from){
dfn[x]=low[x]=++tot;
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].v;
if(!dfn[v]){
tarjan(v,i);
low[x]=min(low[x],low[v]);
if(low[v]>dfn[x])//!!
bridge[i]=bridge[i^1]=1;
}
else if((i^1)!=from)low[x]=min(low[x],dfn[v]);//!!
}
}
割點
inline void tarjan(int x){
dfn[x]=low[x]=++tot;
int fl=0;
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
if(low[v]>=dfn[x]){//!!
fl++;
if(x!=root||fl>1)cnt[x]=1;//root特判
}
}
else low[x]=min(low[x],dfn[v]);
}
}
邊雙聯通分量
inline void tarjan(int x,int from){
dfn[x]=low[x]=++tot;
st[++ttp]=x;
for(re int i=first[x],v;i;i=e[i].nxt){
if((i^1)==from)continue;
v=e[i].v;
if(dfn[v])low[x]=min(low[x],dfn[v]);
else{
tarjan(v,i);
low[x]=min(low[x],low[v]);
}
}
if(low[x]==dfn[x]){//縮點
cn++;int v;
do{
v=st[ttp--];
dcc[v]=cn;
num[cn]+=a[v];
}while(v!=x);
}
}
也可以先求割邊,刪去後,各個聯通塊
點雙聯通分量
inline void tarjan(int x){
dfn[x]=low[x]=++tot;
st[++top]=x;
if(x==rt&&first[x]==0){//孤立點
dcc[++cn].push_back(x);
return;
}
int fl=0;
for(int i=first[x],v,u;i;i=e[i].nxt){
v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
if(low[v]>=dfn[x]){
fl++;
if(x!=root||fl>1)cnt[x]=1;
cn++;
do{//!!
u=st[top--];
dcc[cn].push_back(u);
}while(u!=v);
dcc[cn].push_back(x);//割點可能在多個點雙內
}
}
else low[x]=min(low[x],dfn[v]);
}
}
縮點後一般將割點與包含它的所有連邊,形成一棵樹
歐拉路問題
歐拉回路:一筆畫,起點終點相同,所有點度數爲偶數
歐拉通路:一筆畫,除起點終點度數爲奇數外,其餘點爲偶數
輸出具體方案:
inline void dfs(int x){
for(int i=first[x];i;i=e[i].nxt){
if(vis[i])continue;
vis[i]=vis[i^1]=1;
dfs(e[i].v);
st[++top]=e[i].v;
}
}
int main(){
……
dfs(s)
cout<<1<<" ";//??
for(int i=top;i;i--)//貌似也不用倒序
cout<<st[i]<<" ";
return (0-0);
}
tarjan與有向圖連通性
有向圖的強連通分量
inline void tarjan(int x){
dfn[x]=low[x]=++tot;
st[++top]=x;vis[x]=1;
for(int i=first[x],v;i;i=e[i].nxt){
v=e[i].w;
if(!dfn[v]){
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v])low[x]=min(low[x],dfn[v]);//!!
}
if(dfn[x]==low[y]){
cn++;int u;
do{
u=st[top--];
vis[u]=0;
scc[u]=cn;
sc[cn].push_back(u);
}while(u!=x);
}
}
有向圖的必經邊必經點
從起點到終點必經的點叫必經點,同理也有必經邊
有環的有向圖需用支配樹來解,這裏不討論
有向無環圖的必經點與必經邊:
- 原圖中拓撲序動態規劃,起點到每個點的路徑條數爲
- 反圖中同理求出終點到每個點的路徑條數
- 必經邊滿足
- 必經點滿足
2-SAT
我們通俗的說,就是給你個變量,每個變量能且只能取的值。同時給出若干條件,形式諸如,其中表示中的一種
我們把每個點拆爲兩個,用若則的方式連邊,同時也要連逆否命題
求解時染色,若矛盾或一個點的兩種狀態連通則無解,不然可以隨便求出一組解
(有時關係不太好描述,如&,可直接製造矛盾)