从拓扑排序到2-sat

从拓扑排序到2-sat

  1. 拓扑排序

    1. #include<iostream>
      #include<stdio.h>
      #include<string.h>
      #include<algorithm>
      #define maxn 100005
      using namespace std;
      int n,m,tot;
      int deg[maxn];
      int q[maxn];
      int head[maxn],nex[maxn],to[maxn];
      void add(int x,int y)
      {
      	to[++tot]=y;nex[tot]=head[x];head[x]=tot;
      }
      int main()
      {
      	scanf("%d%d",&n,&m);
      	for(int i=1;i<=m;i++)
      	{
      		int x,y;
      		scanf("%d%d",&x,&y);
      		add(x,y);
      		deg[y]++;
      	}
      	int hd=0,tl=-1;
      	for(int i=1;i<=n;i++)
      		if(!deg[i])
      			q[++tl]=i;
      	while(hd<=tl)
      	{
      		int now=q[hd]; hd++;
      		for(int i=head[now];i;i=nex[i])
      		{
      			deg[to[i]]--;
      			if(!deg[to[i]])
      				q[++tl]=to[i];
      		}
      	}
      	for(int i=0;i<n;i++)
      		printf("%d ",q[i]);
      }
      
      1. 求字典序最小解
        1. 先取出度为0编号最大的即可。
    2. 2-sat

      1. 2-sat 是一种通过图上连边的依赖关系来解决如“选a必须选b”,“a和b至少得选一个”之类的问题。

      2. 具体实现:

        1. a和b至少得选一个为例
          1. 我们对每个点拆成两个点 a1,a2,b1,b2表示对应的点选或者不选,那么要是我们a没有选,那么b就必须选了,所以从a2向b1连边即可。 b同理。
        2. a必选
          1. 那么我们从a2向a1连边,由于一个点选和不选不能在同一个集合,所以我们肯定不会选a2,这样就可以满足了。
      3. 输出一组方案

        1. 显然这是一个依赖关系,中间可能存在环,所以我们先要缩点,如果一个点选和不选都缩到一个点去了,说明不存在可行解,否则我们按照原图反向建图,这样依赖关系底层的点是随便选,我们随便一选,然后根据一个点选和不选只能选一个,在拓扑排序的时候递推即可。

        2. 还有一种很妙的方法,由于我们缩点的顺序就是拓扑序,那么我们对于每个点,一个点代表选和不选的那个节点所在强联通分量集合字典序较小的即为原图拓扑序在后的那一个。贪心的选择这个即可。

        3. 代码

          #include<iostream>
          #include<stdio.h>
          #include<string.h>
          #include<algorithm>
          #include<vector>
          #include<stack>
          #include<queue>
          #define maxn 200005
          using namespace std;
          stack<int>s;
          int n,m,cnt,tot,num,ans,sum;
          priority_queue<int,vector<int>,greater<int> > q;
          vector<int>edge1[maxn],edge2[maxn];
          int pri[maxn],pos[maxn],deg[maxn];
          int dfn[maxn],low[maxn],bel[maxn],vis[maxn];
          int getnum()
          {
          	int now=0;char ch=getchar();
          	while(ch<'0' || ch>'9') ch=getchar();
          	while('0'<=ch && ch<='9') {now=now*10+ch-'0';ch=getchar();}now*=2;
          	if(ch=='h') now++;
          	return now;
          }
          int myabs(int now)
          {
          	return now>0 ? now : -now;
          }
          void add(int u,int v)
          {
          	edge1[u].push_back(v);
          }
          void tarjan(int now)
          {
          	vis[now]=1;
          	s.push(now);
          	dfn[now]=low[now]=++cnt;
          	int len=edge1[now].size();
          	for(int i=0;i<len;i++)
          	{
          		int nex=edge1[now][i];
          		if(!dfn[nex])
          		{
          			tarjan(nex);
          			low[now]=min(low[now],low[nex]);
          		}
          		else if(vis[nex])
          		{
          			low[now]=min(low[now],dfn[nex]);
          		}
          	}
          	if(dfn[now]==low[now])
          	{
          		int nex;
          		num++;
          		do
          		{
          			nex=s.top();s.pop();
          			vis[nex]=0;
          			bel[nex]=num;
          		}while(nex!=now);
          	}
          }
          void toposort()
          {
          	for(int i=1;i<=num;i++)
          	if(deg[i]==0)
          	q.push(i);
          	while(!q.empty())
          	{
          		int now=q.top();q.pop();
          		if(!pri[now]) pri[now]=1,pri[pos[now]]=-1;
          		int len=edge2[now].size();
          		for(int i=0;i<len;i++)
          		{
          			int nex=edge2[now][i];
          			deg[nex]--;
          			if(!deg[nex])
          			q.push(nex);
          		}
          	}
          }
          int check()
          {
          	for(int i=0;i<2*n;i+=2)
          	{
          		if(bel[i]==bel[i^1]) return false;
          		pos[bel[i]]=bel[i^1];
          		pos[bel[i^1]]=bel[i];
          	}
          	for(int i=0;i<2*n;i++)
          	{
          		int len=edge1[i].size();
          		for(int j=0;j<len;j++)
          		{
          			int nex=edge1[i][j];
          			if(bel[i]!=bel[nex])
          			{
          				deg[bel[i]]++;
          				edge2[bel[nex]].push_back(bel[i]);
          			}
          		}
          	}
          	toposort();
          	return true;
          }
          int main()
          {
          	while(scanf("%d%d",&n,&m) && (n||m))
          	{
          		while(!s.empty()) s.pop();
          		while(!q.empty()) q.pop();
          		cnt=tot=num=ans=sum=0;
          		for(int i=0;i<=2*n;i++)
          		{
          			edge1[i].clear();edge2[i].clear();
          			pri[i]=dfn[i]=low[i]=bel[i]=vis[i]=deg[i]=pos[i]=0;
          		}
          		
          		for(int i=1;i<=m;i++)
          		{
          			int x,y;
          			x=getnum();y=getnum();
          			add(x,y^1);add(y,x^1);
          		}
          		
          		add(0,1);
          		
          		for(int i=0;i<2*n;i++)
          		if(!dfn[i])
          		tarjan(i);
          	
          		if(!check())
          		{
          			printf("bad luck\n");
          			continue;
          		}
          		
          		for(int i=1;i<n;i++)
          		{
          			if(pri[bel[i*2+1]]==-1) printf("%dh",i);
          			else printf("%dw",i);
          			if (i<n-1) printf(" ");else printf("\n");
          		}
          	}
          } 
          
      4. 输出字典序最小解

        1. 从小到大枚举每个点是否可行,如果它存在可行解,就把它钦定必选,后面跟着判断即可。

        2. 代码

          #include<iostream>
          #include<stdio.h>
          #include<string.h>
          #include<algorithm>
          #include<vector>
          #define maxn 200005
          using namespace std;
          int n,m,cnt;
          vector<int>edge[maxn];
          int s[maxn],match[maxn];
          void add(int x,int y)
          {
              edge[x].push_back(y);
          }
          int dfs(int now)
          {
              if(match[now^1]) return false;
              if(match[now]) return true;
              s[++cnt]=now;match[now]=true;
              int len=edge[now].size();
              for(int i=0;i<len;i++)
              {
                  int nex=edge[now][i];
                  if(!dfs(nex)) return false;
              }
              return true;
          }
          int can()
          {
              memset(match,0,sizeof(match));
              for(int i=0;i<2*n;i+=2)
              {
                  int now=i,nex=i^1;
                  if(!match[now] && !match[nex])
                  {
                      cnt=0;
                      if(!dfs(now))
                      {
                          for(int j=1;j<=cnt;j++)
                          match[s[j]]=false;
                          if(!dfs(nex))
                          return false;
                      }    
                  }
              }
              return true;
          }
          int main()
          {
              while(~scanf("%d%d",&n,&m))
              {
                  for(int i=0;i<2*n;i++)
                  edge[i].clear();
                  for(int i=1;i<=m;i++)
                  {
                      int x,y;
                      scanf("%d%d",&x,&y);
                      x--;y--;
                      add(x,y^1);
                      add(y,x^1);
                  }
                  if(can())
                  {
                      for(int i=0;i<2*n;i+=2)
                      {
                          if(match[i]) printf("%d\n",i+1);
                          else printf("%d\n",i+2);
                      }
                  }
                  else printf("NIE\n");  
              }
          } 
          
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值