图论模板总结

图论模板总结

为什么图论也有那么多代码??


一.最小生成树

1.最小生成树

prim

下面的写法是O(eloge)

有O(n^2)的写法:双重循环枚举每个点

typedef pair<int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> >q;
void prim(){
	memset(dis,inf,sizeof(dis));
	dis[1]=0;q.push(make_pair(0,1));//!!
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;
		vis[u]=1; ans+=dis[u];//!!
		for(int i=head[u];i!=-1;i=e[i].nxt){
			int v=e[i].v;
			if(!vis[v]&&dis[v]>e[i].w){
				dis[v]=e[i].w;
				q.push(make_pair(dis[v],v));
			}
		}
	}
}

kruskal

O(eloge)

struct edge{
	int u,v,w;
	bool operator < (const edge &b) const{
		return w<b.w;
	}
}e[M];
int Find(int x){return (fa[x]==x)?x:fa[x]=Find(fa[x]);}
void kruskal{
	sort(e+1,e+m+1);
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=m;++i){
		int x=Find(e[i].u),y=Find(e[i].v);
		if(x!=y){
			fa[x]=y;
			ans+=e[i].w;
			if(++cnt==n-1) break;
		}
	}
	printf("%d\n",ans);
}

2.次小生成树

1.次小生成树

Qin Shi Huang’s National Road System
HDU - 4081
题意:有n个(n<=1000)城市,告诉坐标(int),边的权值就是两点的距离,并且每个城市都有人居住,现在要修路n-1条路,使得每个城市都连通。现能让一条边可以不用任何花费。求这条边的两端点的总人数/(包含这条边的最小生成树的总权值-这条边的权值(这条边花费为0时最小生成树的总权值))最大值。即(Wa+Wb)/(mst-w(a,b))最大。

观察上式,分母上是一条边的两个端点。分子上是包含这条边的最小生成树。我们考虑枚举每条边,如果它在MST上,就已经解决了。如果不在MST上,我们就找出加上它所形成的那个环,减去环上的最大边即可。

这里隐含了次小生成树的求法:枚举每条不在最小生成树上的边,加入树中,减掉环上的最大边。O(n^2)

sort(e + 1, e + k);
for(int i = 1; i < k; ++i){
	int xx = find(e[i].u),yy = find(e[i].v);
	if(xx != yy){
		for(int a = head[xx]; a; a = ns[a].nxt)
			for(int b = head[yy]; b; b = ns[b].nxt)
		    	dis[ns[a].v][ns[b].v] = dis[ns[b].v][ns[a].v]=e[i].w;//因为是树,所以最短路唯一
     	ans += e[i].w;
		fa[yy] = xx;
		ns[endd[yy]].nxt = head[xx];//!!
		head[xx] = head[yy];//!!
		if(++cnt == n - 1) break;
	}
}

3.最优比率生成树

01分数规划。

直接二分答案。重新给边权赋值,然后跑kruskal即可。

4.K度最小生成树

POJ 1639 Picnic Planning
求一个无向图的最小生成树,其中有一个点的度有限制(假设为 k)。

要求k度最小生成树,我们可以按照下面的步骤来做:

设有度限制的点为 RT。

1,把所有与RT相连的边删去,图会分成多个子图(假设为m个,如果m>k,那么问题无解),在内部分别求最小生成树;

然后用最小的代价将 m 个最小生成树和 RT连起来,那我们就得到了一棵关于RT的m度最小生成树。

2,在m度生成树中找一个和RT相连的点,把这条边加入MST,会生成一个环,删去环上的最大边。

完成一次 2 操作后得到的是m+1度最小生成树,以此类推,直到得到k度最小生成树。

5.最短路径生成树

51nod 1443
求一个图的一棵生成树,使得在生成树上从u到任一点的最短距离等于原图中从u到任一点的最短距离。这样的生成树叫最短路径树。你的任务是找出从u开始的最短路径树,并且这个树中所有边的权值之和要最小。

这类题一定要分清重要性。

题意是在满足最短路长度相同的情况下,生成树的权最小。所以应该先跑最短路,在最短路的基础上考虑第二个条件。

有两种做法:

1.由于生成树中的每条边会给树带来一个点,所以每个点的贡献就是最短路上的最小前驱。

2.先跑最短路,再记录所有可能是最短路上的边,然后跑最小生成树。

这是第一种做法。

不开longlong见祖宗。

void dijkstra(int s){
	memset(dis,inf,sizeof(dis));
	dis[s]=0;q.push(make_pair(0,s));
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];i!=-1;i=e[i].nxt){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				val[v]=e[i].w;
				q.push(make_pair(dis[v],v));
			}
			else if(dis[v]==dis[u]+e[i].w&&val[v]>e[i].w){
				val[v]=e[i].w;
			}
		}
	}
}

int a,b,c;

int main(){
//	freopen("a.txt","r",stdin);
	memset(head,-1,sizeof(head));
	n=read();m=read();
	for(int i=1;i<=m;++i){
		a=read();b=read();c=read();
		adde(a,b,c);adde(b,a,c);
	}
	s=read();
	dijkstra(s);
	for(int i=1;i<=n;++i){
		ans+=val[i];
	}
	write(ans);putchar('\n');
	return 0;
}

CH6202
求一个图中最短路径生成树的个数。

首先用最短路算法求出从u到任一点的最短路。然后将所有节点按dis从小到大排序。像Prim一样依次把每个节点P加入生成树。

统计有多少已经在树上的节点x有边连向P,且满足dis[p]=dis[x]+d[x][p].累乘即可。

二.最短路

1.最短路

dijkatra

vis[]记录每个点是否进入最短路集合。

typedef pair<int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> >q;
void dijkstra(){
	memset(dis,INF,sizeof(dis));
	dis[s]=0;q.push(make_pair(dis[s],s));
	while(!q.empty()){
		int u=q.top().second;q.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=head[u];i!=-1;i=e[i].nxt){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				q.push(make_pair(dis[v],v));
			}
		}
	}
}

spfa

vis[u]记录的是u点是否入队。

queue <int> q;
void spfa(int s){
//必须赋成INF
	memset(dis,INF,sizeof(dis));
	while(!q.empty()) q.pop();
	q.push(s);in[s]=1;
	while(!q.empty()){
		int u=q.front();q.pop();
		vis[u]=0;
		for(int i=head[u];i!=-1;i=e[i].nxt){
			int v=e[i].v;
			if(dis[v]>dis[u]+e[i].w){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v]){
					vis[v]=1;q.push(v);
					//判断负环
					if((++in[v])>=n){
						flg=1;return;
					}
				}
			}
		}
	}
}

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]=dis[i][k]+dis[k][j];

2.次短路

Roadblocks POJ - 3255
题意:求出1 -> n的次短路;(1 ≤ N ≤ 5000)

思路:

1.可以跑两遍最短路,从1 -> n 和 n -> 1 因为次短路肯定是替换最短路上的一条边后形成的,所以之后直接枚举每条边替换就行了。(在跑n -> 1的时候要建反向图)

2.在dijkstra的时候同时更新最短和次短。(推荐!)

区别于“从城市s1到t1不超过l1小时,并且从城市s2到t2不超过l2小时”那题,那题是必须枚举两条最短路重合路径

queue <int> q;
void spfa1(){
	memset(dis1,inf,sizeof(dis1));
	dis1[1] = 0;q.push(1);
	while(!q.empty())	{
		int u = q.front();q.pop();vis[u] = 0;
		for(int i = head[u]; i ; i = e[i].nxt)	{
			int v = e[i].v;
			if(dis1[v] > dis1[u] + e[i].w){
				dis1[v] = dis1[u] + e[i].w;
				if(!vis[v])	{
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
}
void spfan(){
	memset(dis2,inf,sizeof(dis2));
	dis2[n] = 0;q.push(n);
	while(!q.empty()){
		int u = q.front();q.pop();vis[u] = 0;
		for(int i = head[u]; i ; i = e[i].nxt){
			int v = e[i].v;
			if(dis2[v] > dis2[u] + e[i].w){
				dis2[v] = dis2[u] + e[i].w;
				if(!vis[v])	{
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&r);
	for(int i = 1; i <= r; ++i){
		scanf("%d%d%d",&a,&b,&c);
		adds(a,b,c); adds(b,a,c);
	}
	spfa1();spfan();ans = dis1[n];
	for(int i = 1; i < k; ++i)
		if(dis1[e[i].u] + dis2[e[i].v] + e[i].w > ans) 
			cnt = min(dis1[e[i].u] + dis2[e[i].v] + e[i].w,cnt);
	printf("%d",cnt);
	return 0;
}

3.最小环

1.无向图的最小环:FLOYD

path记录了最小环的路径。

vector <int> path;
void get_path(int x,int y){
	if(pos[x][y]==0)return;
	get_path(x,pos[x][y]);
	path.push_back(pos[x][y]);
	get_path(pos[x][y],y);
}
int main(){
	n=read();m=read();
	memset(a,INF,sizeof(a));
	for(int i=1;i<=n;++i) a[i][i]=0;//!!
	for(int i=1,x,y,z;i<=m;++i){
		x=read();y=read();z=read();
		a[x][y]=a[y][x]=min(a[x][y],z);
	}
	memcpy(d,a,sizeof(a));
	for(int k=1;k<=n;++k){
		for(int i=1;i<k;++i)
			for(int j=i+1;j<k;++j)
				if((ll)d[i][j]+a[j][k]+a[k][i]<ans){
					ans=d[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(d[i][j]>d[i][k]+d[k][j]){
					d[i][j]=d[i][k]+d[k][j];
					pos[i][j]=k;
				}
	}
	if(ans==INF){
		puts("No solution.");
		return 0;
	}
	int sz=path.size();
	for(int i=0;i<sz;++i)	printf("%d ",path[i]);
	return 0;
}

2.有向图的最小环

0 or 1 HDU - 4370
题意:给你一个矩阵C,让你构造一个01矩阵X使 ∑ i = 1 , j = 1 i &lt; = n , j &lt; = n C i j ∗ X i j ∑_{i=1,j=1}^{i&lt;=n,j&lt;=n}Cij*Xij i=1,j=1i<=n,j<=nCijXij最小,输出这个最小值。
构造出来的X矩阵需要满足一下关系:
1.$X_{12}+X_{13}+…+X_{1n}=1 $
2.$X_{1n}+X_{2n}+…X_{n-1n}=1 $
3.对于任意的 i ( 1 &lt; i &lt; n ) i (1 &lt; i &lt; n) i1<i<n, 满足 $\sum{X_{ki}(1<=k<=n)} =\sum{X_{ij}(1<=j<=n)} $.

思维题:

将矩阵C看做描述N个点的邻接矩阵

再来看三个条件:

条件一:表示1号点出度为1

条件二:表示n号点入度为1

条件三:表示k( 1 < k < n )号点出度等于入度

由此题目要求的 ∑ i = 1 , j = 1 i &lt; = n , j &lt; = n C i j ∗ X i j ∑_{i=1,j=1}^{i&lt;=n,j&lt;=n}Cij*Xij i=1,j=1i<=n,j<=nCijXij就变为从一个完全图中选一些边,形成以下两种路径之一:

1.1号点到n号点的花费

2.形成一个含1号点的环(非自环)和一个含n号点的环(非自环)的花费之和(因为1到1号点和n号点到n号点是不受限制的)

一个包含u的环的最小花费计算方式如下:(用dijkstra或spfa做基础均可)

将u直达的v的dis赋成w[u,v]然后入队,其余所有点的dis赋成inf(包括u本身,特判自环),通过最短路算法的松弛,dis[u]即为包含u的最小环的花费。

void init(int s){
	for(int i = 1; i <= n; ++i){
		if(i == s) 	dis[i] = inf;
		else if(g[s][i] != inf) {
			dis[i] = g[s][i];
			q.push(i);
		}
		else 	dis[i] = inf;
	}
}
void spfa(int s){
	init(s);
	while(!q.empty()){
		int u = q.front();q.pop();vis[u] = 0;
		for(int v = 1; v <= n; ++v){
			if(v == u) continue;
			if(dis[v] > dis[u] + g[u][v]){
				dis[v] = dis[u] + g[u][v];
				if(!vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
}
int main(){
	while(~scanf("%d",&n)){
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
				scanf("%d",&g[i][j]);
		spfa(1);//在求包含1号点的最小环时顺带求出从1到n的路径。
		ans = dis[n];
		tmp = dis[1];
		spfa(n);
		ans = min(ans,dis[n] + tmp);
		printf("%d\n",ans);
	}
}

4.传递闭包

与topsort的区别在于传递闭包是计算当前点和所有点的关系。而topsort是top序的关系

Cow Contest POJ - 3660 题意:有n头牛比赛,m个比赛结果,最后问你一共有多少头牛的排名被确定了,其中如果a战胜b,b战胜c,则也可以说a战胜c。求能确定排名的牛的数目。(1 ≤ N ≤ 100)
数据保证结果不存在矛盾。

第一想法肯定是拓扑排序的,但是这题求的不是a输给哪几头牛,而是a输给了某些牛且战胜了其余的所有牛。

所以我们考虑一头牛被x头牛打败,打败y头牛(即入度x出度y)且x+y=n-1,则这头牛的排名就被确定了。

所以我们只要将任何两头牛的胜负关系确定了,在遍历所有牛判断一下是否满足x+y=n-1,将满足这个条件的牛数目加起来就是所求解。

任意两头牛的胜负关系是简单的传递闭包问题,极小的数据范围可以用folyd解决。

int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= m; ++i)	{
		scanf("%d%d",&a,&b);
		dis[a][b] = 1;
	}
	for(int k = 1; k <= n; ++k)
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= n; ++j)
				dis[i][j] |= (dis[i][k] && dis[k][j]);
	for(int i = 1; i <= n; ++i)	{
		cnt = 0;
		for(int j = 1; j <= n; ++j)
			if(dis[i][j] || dis[j][i]) ++cnt;
		if(cnt == n - 1) ++ans; 
	}
	printf("%d",ans);
	return 0;
}

5.同余最短路

51NOD 1326 遥远的旅途
一个国家有N个城市,这些城市被标为0,1,2,…N-1。这些城市间连有M条道路,每条道路连接两个不同的城市,且道路都是双向的。一个小鹿喜欢在城市间沿着道路自由的穿梭,初始时小鹿在城市0处,它最终的目的地是城市N-1处。小鹿每在一个城市,它会选择一条道路,并沿着这条路一直走到另一个城市,然后再重复上述过程。每条道路会花费小鹿不同的时间走完,在城市中小鹿不花时间逗留。路程中,小鹿可以经过一条路多次也可以经过一个城市多次。给定城市间道路的信息,问小鹿是否有一种走法,从城市0出发到达城市N-1时,恰好一共花费T个单位的时间。如果存在输出“Possible”,否则输出“Impossible”。
注意,小鹿在整个过程中可以多次经过城市N-1,只要最终小鹿停在城市N-1即可。

把每一条某一个端点为0的边当成环,定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为到达第i号点,路程%2w为j的路径的最小值。

用spfa跑最短路更新。如果 d p [ i ] [ L % 2 w ] &lt; = L dp[i][L\%2w]&lt;=L dp[i][L%2w]<=L,那么一定能通过补环成为L.

但是这是单向的。为什么所有这样的边都不满足就可以说无法到达呢:spfa的更新方式把所有在0到n-1路径上的环就跑了,只差与1相连的环。

如:用 d p [ u ] [ i ]    − &gt;    d p [ v ] [ ( i + w ) % K ]    − &gt;    d p [ u ] [ ( i + 2 ∗ w ) % k ] dp[u][i]\ \ -&gt;\ \ dp[v][(i+w)\%K]\ \ -&gt;\ \ dp[u][(i+2*w)\%k] dp[u][i]  >  dp[v][(i+w)%K]  >  dp[u][(i+2w)%k]

typedef pair<ll,ll> pll;
queue <pll> q;
void spfa(ll k){
	memset(dis,INF,sizeof(dis));
	q.push(make_pair(1LL,0LL));dis[1][0]=0;
	while(!q.empty()){
		ll u=q.front().first;ll d=q.front().second;q.pop();
		vis[u][d]=0;
		for(ll i=head[u];i!=-1;i=e[i].nxt){
			ll v=e[i].v,t=(d+e[i].w)%k;
			if(dis[v][t]>dis[u][d]+e[i].w){
				dis[v][t]=dis[u][d]+e[i].w;
				if(!vis[v][t]){
					q.push(make_pair((ll)v,(ll)t));
					vis[v][t]=1;
				}
			}
		}
	}
}
int main(){
	T=read();
	while(T--){
		memset(head,-1,sizeof(head));kk=0;
		flg=0;
		n=read();m=read();L=read();
		for(ll i=1,a,b,c;i<=m;++i){
			a=read()+1;b=read()+1;c=read();
			adde(a,b,c);adde(b,a,c);
		}
		for(ll i=0;i<kk;++i)
			if(e[i].v==1){
				spfa(2*e[i].w);
				if(dis[n][L%(2*e[i].w)]<=L){
					flg=1;
					break;
				}
			}
		if(flg) puts("Possible");
		else puts("Impossible");
	}
	return 0;
}

拓扑排序

这类题一般topsort,并查集,folyd传递闭包

确定比赛名次 HDU - 1285
大意:有N个比赛队(1<=N<=500),编号依次为1,2,3,,,N进行比赛。现在知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。如果无法区分就按字典序从小到大输出。现在请你编程序确定队伍的排名。

topsort+priority_queue

tarjan

无向图的割点与桥

割点的判断:

u是割点:当且仅当u存在一个儿子v使得low[v]>=dfn[u].

因为是大于等于,所有不用考虑重边和父节点的问题。

cnt[]表示如果u是割点,割掉它后形成的联通块数目。

void tarjan(int u,int fa){
	dfn[u] = low[u] = ++idx;
	int son = 0;
	for(int i = head[u]; i != -1; i = e[i].nxt){
	    int v=e[i].v;
		if(!dfn[v]){
			tarjan(v,u);
			low[u] = min(low[u],low[v]);
			++son;
			if(u != rt && low[v] >= dfn[u])	cnt[u]++;
		}
		else low[u] = min(low[u],dfn[v]);
	}
	if(u == rt && son >= 2) cnt[u] = son; 
	else cnt[u]++;
	if(cnt[u] >= 2) cut++; 
}

桥的判断:

u到v的边是桥:当且仅当这条边连接的儿子low[v]>dfn[u].

所以需要考虑父节点和重边的问题。优秀的解决方案是记录进入边的编号。

void Tarjan(int u,int in_edge){
	dfn[u]=low[u]=++idx;
	for(int i=head[u];i!=-1;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]){
			Tarjan(v,i);
			low[x]=min(low[x],low[y]);
			
			if(low[y]>dfn[x])
				bri[i]=bri[i^1]=1;
		}
		else if(i!=(in_edge^1))
			low[u]=min(low[u],dfn[v]);
	}
}

无向图的双联通分量(点双联通,边双联通)

点双联通:不存在割点。

边双联通:不存在桥。

点双联通:当且仅当满足下列俩条件之一:

1.图的顶点数不超过2

2.图中任意两点都同时包含在至少一个简单环中。

边双联通:当且仅当任意一条边都包含在至少一个简单环中。

点双联通:缩点后一个割点可能存在于多个联通块中。

POJ 3177 Redundant Paths
题意:有n个牧场,Bessie要从一个牧场到另一个牧场,要求至少要有2条独立的路可以走。现已有m条路,求至少要新建多少条路,使得任何两个牧场之间至少有两条独立的路。两条独立的路是指:没有公共边的路,但可以经过同一个中间顶点。

在点双联通分量缩点之后,每次去连一条链的两端(就是度为1的,设有sum个)。就是 s u m + 1 2 \frac{sum+1}{2} 2sum+1

注意点双联通就需要判断父亲

void Tarjan(int u,int fa){
	sta[++tp]=u;ins[u]=1;
	dfn[u]=low[u]=++idx;
	for(int i=head[u];i!=-1;i=e[i].nxt){
		if(i==(fa^1)) continue;//!!
		int v=e[i].v;
		if(!dfn[v]){
			Tarjan(v,i);
			low[u]=min(low[u],low[v]);
		}
		else low[u]=min(low[u],dfn[v]);
	}	
	if(dfn[u]==low[u]){
		++cnt;
		while(1){
			int v=sta[tp--];ins[v]=0;
			col[v]=cnt;
			if(u==v) break;
		}
	}
}
int main(){
//	freopen("a.txt","r",stdin);
	memset(head,-1,sizeof(head));
	n=read();m=read();
	for(int i=1,a,b;i<=m;++i){
		a=read();b=read();
		adde(a,b);adde(b,a);
	}
	
	Tarjan(1,-1);
	
	for(int i=1;i<=n;++i)
		for(int j=head[i];j!=-1;j=e[j].nxt){
			int v=e[j].v;
			if(col[i]!=col[v])	d[col[i]]++;
		}
	for(int i=1;i<=cnt;++i)
		if(d[i]==1) sum++;

	printf("%d\n",(sum+1)/2);
	return 0;
}

错误做法:利用双连通分量内的点low[]值都是相同的,在dfs时,用low[u]=min(low[u],low[v]),(即用low[v]替代dfn[v]),就不缩点,在求度数时,枚举每条边判断low[u]是否等于low[v],若low[u]!=low[v],则不是同一个边双连通分量,度数+1。

从一开始就是错的。关键在于同一个边双连通分量内的点low[]值不一定相同.如果这个边双联通分量中有多个环,就不成立。详见参考。

数据
4 5
1 2
1 3
2 3
2 4
3 4

边双联通:去掉所有桥之后再跑图即可。桥不存在于任何联通块。

有向图的强连通分量

就是点双联通分量的有向图版本。因为是有向图,就不用担心重边和父节点的问题。

void tarjan(int u){
	s.push(u);ins[u] = 1;
	dfn[u] = low[u] = ++idx;
	for(int i = head[u]; i != -1; i = e[i].nxt){
		if(!dfn[e[i].v]){
			tarjan(e[i].v);
			low[u] = min(low[u],low[e[i].v]);
		}
		else if(ins[e[i].v]) low[u] = min(low[u],dfn[e[i].v]);
	}
	if(dfn[u] == low[u]){
		++scc;
		while(s.top() != u) {
		    ins[s.top()] = 0;
		    s.pop();
		}
		s.pop();//u要出栈。
	}
}

2-SAT问题

什么是2-sat问题
有n个布尔型变量xi,另外m个需要满足的条件。每个条件都是“xi为真/假或者xj为真/假”。这句话中的“或者”意味着两个条件中至少有一个正确。2-sat问题的目标是给每个变量赋值,使得所有的条件得到满足。
算法的大致过程是这样的:
构造一张有向图G,其中每个变量拆成两个结点2i和2i+1,分别表示xi为假和xi为真。最后要为每个变量选其中一个结点标记。
对于每个“xi为假或者xj为假"这样的条件,我们连两条对称的有向边。我们上面说过,或者意味着两个中间至少有一个正确。所以如果xi为真的话,那么xj一定为假,所以我们从2i+1向2j连一条有向边。同样的道理,我们也从2j+1向2i连一条有向边。
接下来我们逐一考虑每个没有赋值的变量,设为xi。我们先假定它为假,然后标记结点2i,并且沿着有向边标记所有能标记的结点。如果标记的过程中发现某个变量对应的两个结点都被标记,则xi假这个假定不成立,需要改为xi为真,然后重新标记。

这种方法太过暴力,只适用于求字典序最小的时候。

所以我们可以用Tarjan优化。求出图中的强连通分量。如果真假两个节点在同一个联通块中就无解。否则有解。

注意事项:

1.有向图 or 无向图
2.图是否一定联通
3.kk有无清零,head有没有赋成-1
4.队列有无pop()

参考:
https://www.cnblogs.com/chenchengxun/p/4718736.html
http://www.cnblogs.com/LQLlulu/p/9310860.html
https://blog.csdn.net/M_GSir/article/details/52892381

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值