CSP圖論複習

最短路

dijkstra

O((m+n)logn)O((m+n)log_n)

基於貪心思想,不能處理負邊權(每個點第一次出爲最短路,但負邊權若離源點較遠,還沒更新到就已確定了答案)

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

複雜度玄學,最壞O(nm)O(nm)

判負環:cnt[x]cnt[x]表示從1到nn的最短路包含的邊數,cnt[1]=0cnt[1]=0,若發現cnt[v]>=ncnt[v]>=n則有負環(也可以判一個點入隊大於等於nn次,但複雜度低些)

別用雙端隊列優化,卡的更兇,且無法判負環(入隊nn次也不一定是被更新了nn次),dfsdfs版也會被卡

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

任意兩點之間最短路,O(n3)O(n^3)

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];

bitsetbitset

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]

最小環:
floydfloyd中再開一個更新答案

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;
			}
}

也可以設dis[i][i]=infdis[i][i]=inf求完之後就是最小環了

最小生成樹

kruskal

O(mlogm)O(mlog_m)

貪心加入最小的邊

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

O(n2)O(n^2)

適用於稠密圖

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];

最小樹形圖

方法:(朱-劉算法)

  1. 求最短弧集合E

  2. 判斷集合E中有沒有有向環,如果有轉步驟3,否則轉4

  3. 收縮點,把有向環收縮成一個點,並且對圖重新構建,包括邊權值的改變和點的處理,之後再轉步驟1

  4. 展開收縮點,求得最小樹形圖

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);

樹的直徑

  1. 兩遍dfsdfs求出最遠點(負邊權不適用)
  2. 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)

倍增

O(logn)O(log_n)

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];
}

樹剖

O(logn)O(log_n)

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

O(1)O(1)

離線算法

把詢問存儲至每個節點處,若回溯完成則將節點指向父節點,lcalca即爲所指向的節點

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;
}

基環樹

  1. 把環看做一個點
  2. 環形處理方法:破環爲鏈(擴兩倍),強制環上兩點的關係

差分約數

約數條件:xixj<=cx_i-x_j<=c變形xi<=xj+cx_i<=x_j+cdis[i]<=dis[j]+cdis[i]<=dis[j]+c極其相似,可以從jjii連一條cc的邊(若約束過於分散可以加入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);
	}
} 

也可以先求割邊,刪去後,dfsdfs各個聯通塊

點雙聯通分量

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]);
	}
}

縮點後一般將割點與包含它的所有dccdcc連邊,形成一棵樹

歐拉路問題

歐拉回路:一筆畫,起點終點相同,所有點度數爲偶數

歐拉通路:一筆畫,除起點終點度數爲奇數外,其餘點爲偶數

輸出具體方案:

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);
	}
}

有向圖的必經邊必經點

從起點到終點必經的點叫必經點,同理也有必經邊

有環的有向圖需用支配樹來解,這裏不討論

有向無環圖的必經點與必經邊:

  1. 原圖中拓撲序動態規劃,起點到每個點的路徑條數爲fs[x]fs[x]
  2. 反圖中同理求出終點到每個點的路徑條數ft[x]ft[x]
  3. 必經邊(x,y)(x,y)滿足fs[x]ft[y]=fs[T]fs[x]*ft[y]=fs[T]
  4. 必經點xx滿足fs[x]ft[x]=fs[T]fs[x]*ft[x]=fs[T]

2-SAT

我們通俗的說,就是給你nn個變量aia_i,每個變量能且只能取0/10/1的值。同時給出若干條件,形式諸如(not)aiopt(not)aj=0/1(not)a_i opt(not) a_j=0/1,其中optopt表示and,or,xorand,or,xor中的一種

我們把每個點拆爲兩個,用若xxyy的方式連邊,同時也要連逆否命題

求解時染色,若矛盾或一個點的兩種狀態連通則無解,不然可以隨便求出一組解

(有時關係不太好描述,如&,可直接製造矛盾)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章