hdu1816 + POJ 2723开锁(二分+2sat)

题意:
     有m层门,我们在最外层,我们要一层一层的进,每一层上有两把锁,我们只要开启其中的一把们就会开,我们有n组钥匙,每组两把,我们只能用其中的一把,用完后第二把瞬间就会消失,问你最多能开到多少层们。

思路:

      果断二分+2sat,现在我们来看下怎么建边,首先我们把每把钥匙用看成a,不用看成~a ,对于没一组钥匙,我们不能同时选择两个,所以有 x ->~y ,y -> ~x,对于门,我们每次至少选择开一个,所以有 ~x -> y ,~y -> x,就这样二分每次重新建图就行了,顺便说下POJ2723 ,跟这个题目几乎差不多,但是唯一的区别就是那个题目每组钥匙不会重复,这个有可能是同一把钥匙属于多个组,如果用这个题目的代码直接去交POJ2723,直接就可以AC了,钥匙反过来就不一定了,因为那个题目既然说是一把钥匙最多只出现在一组,那么就没有必要把每把钥匙拆成a 和 ~a ,而是把每组的钥匙拆成 a ~a,这样就节省了点数和时间,同时也没有必要建 x ->~y y->~x,这样也节省的边,如果是那么做的,那么到这个题目上就WA了,所以我说这个代码粘到那个代码上肯定AC,反过来就不一定了。


#include<stdio.h>
#include<string.h>
#include<stack>

#define N_node 5000
#define N_edge 50000

using namespace std;

typedef struct
{
   int to ,next;
}STAR;

STAR E1[N_edge] ,E2[N_edge];
int list1[N_node] ,list2[N_node] ,tot;
int Belong[N_node] ,cnt;
int mark[N_node];
int D[N_node][2] ,A[N_node][2];
int id[N_node];
stack<int>st;

void add(int a ,int b)
{
   E1[++tot].to = b;
   E1[tot].next = list1[a];
   list1[a] = tot;
   a = a + b ,b = a - b ,a = a - b;
   E2[tot].to = b;
   E2[tot].next = list2[a];
   list2[a] = tot;
}

void DFS1(int s)
{
   mark[s] = 1;
   for(int k = list1[s] ;k ;k = E1[k].next)
   if(!mark[E1[k].to]) DFS1(E1[k].to);
   st.push(s);
}

void DFS2(int s)
{
   mark[s] = 1;
   Belong[s] = cnt;
   for(int k = list2[s] ;k ;k = E2[k].next)
   if(!mark[E2[k].to]) DFS2(E2[k].to);
}

bool ok(int mid ,int n)
{
   memset(list1 ,0 ,sizeof(list1));
   memset(list2 ,0 ,sizeof(list2));
   tot = 1;
   for(int i = 1 ; i<= n/2 ;i ++)
   {
      int x = A[i][0] * 2 ,xx = A[i][0] * 2 + 1;
      int y = A[i][1] * 2 ,yy = A[i][1] * 2 + 1;
      add(x ,yy) ,add(y ,xx);
   }
     
   for(int i = 1 ;i <= mid ;i ++)
   { 
      int x = D[i][0] * 2 ,xx = D[i][0] * 2 + 1;
      int y = D[i][1] * 2 ,yy = D[i][1] * 2 + 1;
      add(xx ,y) ,add(yy ,x);
   }
   memset(mark ,0 ,sizeof(mark));
   while(!st.empty()) st.pop();
   for(int i = 0 ;i < n * 2 ;i ++)
   if(!mark[i]) DFS1(i);
   memset(mark ,0 ,sizeof(mark));
   cnt = 0;
   while(!st.empty())
   {
      int xin = st.top();
      st.pop();
      if(mark[xin]) continue;
      cnt ++;
      DFS2(xin);
   }
   int mk = 0;
   for(int i = 0 ;i < n * 2 && !mk;i += 2)
   if(Belong[i] == Belong[i^1]) mk = 1;
   return !mk;
}

int main ()
{
   int i ,n ,m ,a ,b;
   while(~scanf("%d %d" ,&n ,&m) && n + m)
   {
      for(i = 1 ;i <= n ;i ++)
      {
         scanf("%d %d" ,&a ,&b);
         A[i][0] = a ,A[i][1] = b;
      }
      for(i = 1 ;i <= m ;i ++)
      scanf("%d %d" ,&D[i][0] ,&D[i][1]); 
      int low ,up , mid ,ans = 0;
      low = 0 ,up = m ,n *= 2;
      while(low <= up)
      {
         mid = (low + up) >> 1;
         if(ok(mid ,n))
         ans = mid ,low = mid + 1;
         else up = mid - 1;
      }
      printf("%d\n" ,ans);
   }
   return 0;
}
         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值