1. 程式人生 > >【NOIp複習】圖論演算法模板合集

【NOIp複習】圖論演算法模板合集

最小生成樹

Kruskal

//Kruskal
struct edge{
    int from,to,val;
}e[maxn];

bool operator < (const edge&a,const edge&b){
    return a.val<b.val;//邊按邊權排序 
}

int find(int a){
    return fa[a]==a ? fa[a] : fa[a]=find(fa[a]);
}

void Kruskal(){
    int cnt=0;
    sort(e+1,e+m+1);
    for(int i=1
,j=0;i<=m,j<n-1;i++){ int a=e[i].from,b=e[i].to; if(find(a)!=find(b)){ //你想幹嘛幹嘛吧 fa[find(a)]=find(b); j++;//加入n-1條邊就可以跳出迴圈啦 } } }

最小完全圖

//最小樹的最小完全圖:
//在Kruskal演算法中,當前新增的邊是連線兩邊所在集合的邊權最小的邊
//所以將邊從小到大考察,該邊左右端點所在並查集連的邊數為f[u]*f[v]-1(因為本來已經有這條邊了嘛)
//然後因為這條邊是最小的,所以其他的邊最小也應該是e[i].val+1 //合併兩邊並查集,搞腚 //在並查集中,用f[]陣列記錄以i為根節點的並查集大小, //union操作實現如下: void Union(int a,int b){ int x=find(a),y=find(b); //以將a合併到b為例 f[y]+=f[x]; fa[x]=y; }

Prim&前向星

typedef pair<int,int> pii;//<dist,idx>,用於堆排序 

int cnt=0,head[maxn],dis[maxn];
bool
vis[maxn]; struct edge{ int to,next,val; }e[maxn*3]; bool cmp(pii a,pii b){ return a.first>b.first;//小根堆 } void add(int u,int v,int val){ for(int i=head[u];~i;i=e[i].next){ if(e[i].to==v){ if(e[i].val>val) e[i].val=val; return; } } e[++cnt].to=v; e[cnt].val=val; e[cnt].next=head[u]; head[u]=cnt; } void Prim(int s){ int ans=0; memset(dis,-1,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pii,vector<pii>,cmp> q; for(int i=head[s];~i;i=e[i].next){ dis[e[i].to]=e[i].val;//初始化與源點相連的所有節點 q.push(make_pair(dis[e[i].to],e[i].to))//入隊待擴充套件 } dis[s]=0; vis[s]=1;//初始化源點 while(!q.empty()){ pii u=q.top(); q.pop(); if(vis[u.second]) continue;//跳過已訪問節點 vis[u.second]=1; ans+=u.first;//用於統計最小生成樹邊權和 for(int i=head[u.second];~i;i=e[i].next){//將與新加入節點相連的所有節點加入堆 int j=e[i].to; if(!vis[j]&&(dis[j]>e[i].val||dis[j]==-1)){//如果目標節點未被訪問且(訪問邊是當前最小的邊或目標節點尚未被訪問過) dis[j]=e[i].val; //就將dis更新為當前邊權,入堆 q.push(make_pair(dis[j],j)); } } } printf("%d\n",ans);//輸出最小生成樹邊權和 }

最短路

SPFA

int dis[maxn];
bool vis[maxn];

bool SPFA(int s){//傳入源點,傳出是否有負環 
    int stat[maxn];//統計節點的入隊次數,大於節點數n說明有負環
    bool flag=0;
    memset(stat,0,sizeof(stat)); 
    memset(dis,0x3f,sizeof(dis));//到源點的距離 
    memset(vis,0,sizeof(vis));  //是否在佇列內 
    queue<int> q;
    q.push(s); vis[s]=1;
    while(!q.empty()){
        if(flag) break;
        int tmp=q.front(); q.pop(); vis[tmp]=0;
        for(int i=head[tmp];~i;i=e[i].next){
            int cur=e[i].to;
            if(dis[cur]>dis[tmp]+e[i].val){
                dis[cur]=dis[tmp]+e[i].val;
                vis[cur]=1; stat[cur]++; 
                if(stat[cur]>n){
                    flag=1; break;
                }
                q.push(cur);
            }
        }
    }
    return flag;
}

Dijkstra

void Dijkstra(int s){
    //堆優化亂搞
    priority_queue<pii> q;
    bool cmp(pii a,pii b){
        return a.first>b.first;
    }
    //init
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    q.push(make_pair(dis[s]=0,s));//初始化源點 
    for(int i=head[s];~i;i=e[i].next){
        int cur=e[i].to;
        dis[cur]=e[i].val; 
        q.push(make_pair(dis[cur],cur));
    }
    while(!q.empty()){
        int tmp=q.top(); q.pop();
        if(vis[tmp]) continue;
        for(int i=head[tmp];~i;i=e[i].next){
            int cur=e[i].to;
            if(!vis[cur]&&dis[cur]>dis[tmp]+e[i].val){
                dis[cur]=dis[tmp]+e[i].val;
                vis[cur]=1; q.push(make_pair(dis[cur],cur));
            }
        }
    }
}

Floyd

普通版

//普通版Floyd
void Floyd(){
    for(int k=1;k<=n;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];
    //在讀入邊的時候邊權作為dis即可 
}

求最小環

//算最小環
//一個環可以拆成一條鏈加一條邊,
//算floyd的時候保證中間點k的編號比i大比j小,就可以保證首尾不經過k點
void Floyd_minimum_cycle(){
    for(int k=1;k<=n;k++){
        for(int i=1;i<k;i++)
            for(int j=k+1;j<=n;j++)
                ans=min(ans,dis[i][j]+map[i][k]+map[k][j]);
        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]);
    }
}

//有向圖求最小環,直接用floyd就可以,以s為起點的環長度為d[s][k]+d[k][s]
//也可以跑Dijkstra先算從s到所有點的最短距離,再算所有點到s的最短距離
//求以s為終點的單(終點)最短路,把圖上所有邊反過來就好

最近公共祖先(LCA)

Tarjan

bool flag[maxn],answered[maxn];
vector<pii> qes;//用來存詢問 

void csh(){//初始化... 
    memset(flag,0.sizeof(flag));//用來標記是否已經算過LCA了
    for(int i=1;i<=n;i++) fa[i]=i;
    memset(head,0,sizeof(head)); 
    memset(answered,0,sizeof(answered));//用來標記是否已經回答過詢問了 
}

void Union(int x,int y){
    fa[find(y)]=find(x); return;//將y集合加入x中 
} 

void LCA(int p,int f){//p為當前節點,f為當前節點的父親(防止倒流...) 
    for(int i=head[p];~i;i=e[i].next){
        if(e[i].to!=f&&!flag[to]){
            LCA(e[i].to,p);
            Union(p,e[i].to);
            flag[to]=1;
        }
    }
    //處理詢問 
}

使用方法:LCA(根節點,0);

RMQ+DFS(倍增)

void dfs(int rt){
    for(int i=head[rt];~i;i=e[i].next){
        if(!dep[e[i].to]){
            dep[e[i].to]=dep[rt]+1;
            p[e[i].to][0]=rt;//p[i][0]為i的直接父節點 
            dfs(e[i].to);
        }
    } return;
} 

void init_bz(){
    //p[i][j]為i節點的第2^j祖先
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i<=n;i++)
            if(p[i][j-1]!=-1)
                p[i][j]=p[p[i][j-1]][j-1];
    return; 
}

int LCA(int a,int b){
    int i,j;
    if(dep[a]<dep[b]) swap(a,b);//保證a是b的兒子節點
    for(i=0;(1<<i)<=dep[a];i++); i--;
    for(j=i;j>=0;j--)
        if(dep[a]-(1<<j)>=dep[b]) a=p[a][j];
    if(a==b) return a;
    for(j=i;j>=0;j--)
        if(p[a][j]!=-1&&p[a][j]!=p[b][j]){
            a=p[a][j]; b=p[b][j];
        } 
    return p[a][0];
}

使用方法:
dfs(1);//1為根節點
dep[1]=0;
p[1][0]=1;

//如果dfs時還要記與根節點距離的話,傳入一個引數pa表示dfs節點的父節點即可 

二分圖

二分圖染色

int col[maxn]; bool flag=1;
void ran(int p,int rt,int c){
    if(col[rt]!=-1&&col[rt]!=c){
        flag=0; return;
    } else {
        for(int i=head[rt];i!=0;i=e[i].next){
            int cur=e[i].to;
            ran(rt,cur,(c+1)%2);
        }
        return;
    }
}

二分圖最大匹配(匈牙利演算法)

讀入優化

inline int read(){
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0'; ch=getchar();
    }
    return x*f;
}//讀入優化來一波~~ 

NTR演算法主體

int nx,ny,match[maxn];
bool vis[maxn],w[maxn][maxn];

bool find(int x){
    for(int i=1;i<=ny;i++){
        if(!w[x][i]||vis[i]) continue;//用vis來標記這個人有沒有被霸佔 
        vis[i]=1;//霸佔該人 
        if(match[i]==-1||find(match[i])){//如果該人沒有匹配,或者匹配更改可行(可NTR) 
            match[i]=x;//更改這個人的匹配到需要找物件的x身上 
            return 1;//NTR成功! 
        }
    }
    return 0;//返回:歌頌愛情的偉大! 
}

int suan(){
    int ans=0;
    memset(match,-1,sizeof(match));//每個人初始化為沒有物件 
    for(int i=1;i<=nx;i++){//對左邊的每一個點嘗試尋找物件 
        memset(vis,0,sizeof(vis));//千萬記住初始化...最開始每個人都沒有被霸佔的 
        if(find(i)) ans++;//可匹配人數++ 
    }
    return ans;
}

int m,a,b;//m是邊的條數,a/b為臨時變數 

int main(){
    nx=read(); ny=read();
    for(int i=1;i<=m;i++){
        a=read(); b=read();
        w[a][b]=1; //這裡預設左右兩邊的編號都是從1開始好了
        //如果題目不一樣的話,只需要簡單修改下就可以了 
    }
    int out=suan();
    if(out) printf("%d\n",out);
    else puts("No.");
    return 0;
}

二分圖最小完備匹配(KM演算法)

不會網路流怎麼破!!!!!!!!!!!!!

int nx,ny,linky[maxn];

double lack,w[maxn][maxn],lx[maxn],ly[maxn];

bool visx[maxn],visy[maxn];

bool find(int x){
    visx[x]=1;
    for(int i=1;i<=ny;i++){
        if(visy[i]) continue;
        int t=lx[x]+ly[i]-w[x][i];
        if(t<0){
            visy[i]=1;
            if(linky[i]==-1||find(linky[i])){
                linky[i]=x;
                return 1;
            }
        } else if(t<lack) lack=t; 
    }
    return 0;
}

double KM(){
    memset(linky,-1,sizeof(linky));
    for(int i=1;i<=ny;i++) ly[i]=0;
    for(int i=1;i<=nx;i++){
        lx[i]=INF;
        for(int j=1;j<=ny;j++)
            if(w[i][j]>lx[i]) lx[i]=w[i][j];
    }
    for(int x=1;x<=nx;x++){
        while(1){
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            lack=INF;
            if(find(x)) break;
            for(int i=1;i<=ny;i++){
                if(visx[i]) lx[i]-=lack;
                if(visy[i]) ly[i]+=lack;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=ny;i++) ans-=w[linky[i]][i];
    return ans;
} 

拓撲排序

Kahn演算法

priority_queue<int,vector<int>,greater<int> > s;//入度為0的集合,小根堆保證字典序最小 
int in[maxn];//存放每個點的入度
vector<int> ans;

void Kahn(){
    for(int i=1;i<=n;i++) if(in[i]==0) s.push(i);
    while(!s.empty()){
        int cur=s.top(); s.pop(); ans.push_back(cur);
        for(int i=head[cur];i!=0;i=e[i].next){
            int tmp=e[i].to;
            in[tmp]--;
            if(in[tmp]==0) s.push(tmp);
        }
    }
    return;
}

強連通分量

%Tarjan

stack<int> s;
bool vis[maxn];
int dfn[maxn],low[maxn],cnt=0,num=0;//時間戳,能回到的最遠祖先,計數器(標記節點),計數器(標記強連通分量) 
int qlt[maxn];//點i屬於哪一個強連通分量 
vector<int> ans[maxn];

void Tarjan(int u){
    dfn[u]=low[u]=++cnt;
    vis[u]=1;
    s.push(u);
    for(int i=head[u];i!=0;i=e[i].next){
        int cur=e[i].to;
        if(!vis[cur]){
            Tarjan(cur);
            low[u]=min(low[u],low[cur]);
        } else if(!qlt[cur]) {
            low[u]=min(low[u],low[cur]);
        }
    }
    if(dfn[u]==low[u]){
        num++;
        while(1){
            int tmp=s.top(); s.pop();
            qlt[tmp]=num;
            if(tmp==u) break;
        }
    }
}