BZOJ3495 [PA2010]Riddle

Address


Solution

  • 很容易想到是 2SAT 2 − S A T 判断是否有解。
  • 满足每条边至少有一个端点是首都很好处理,对于每一条边 (x,y) ( x , y )
    • 不选 x x 就一定要选 y
    • 不选 y y 就一定要选 x
  • 对于每个郡 {a1,a2,...,aw} { a 1 , a 2 , . . . , a w } 只能选一个首都,记点 bi b i 表示该郡前 i i 个点是否被选,则:
    • 选了 ai 就一定要选 bi b i
    • 没选 bi b i 就一定不选 ai a i
    • 选了 bi1 b i − 1 就一定要选 bi b i
    • 没选 bi b i 就一定不选 bi1 b i − 1
    • 选了 ai a i 就一定不选 bi1 b i − 1
    • 选了 bi1 b i − 1 就一定不选 ai a i
    • 按照上述要求建边即可。

    • Code

      #include <cstdio>
      #include <iostream>
      #include <cstring>
      #include <cctype>
      #include <algorithm>
      
      using namespace std;
      
      inline int get()
      {
          char ch; int res = 0; bool flag = false;
          while (ch = getchar(), !isdigit(ch) && ch != '-');
          (ch == '-' ? flag = true : res = ch ^ 48);
          while (ch = getchar(), isdigit(ch))
              res = res * 10 + ch - 48;
          return flag ? -res : res;
      }
      
      const int N = 4e6 + 5, M = 1e7 + 5;
      struct Edge
      {
          int to; Edge *nxt;
      }p[M], *lst[N], *P = p;
      
      int dfn[N], low[N], stk[N], col[N]; bool inv[N];
      int n, m, K, tis, C, top, a[N], b[N];
      
      inline void Link(int x, int y)
      {
          (++P)->nxt = lst[x]; lst[x] = P; P->to = y;
      }
      
      inline void CkMin(int &x, int y) {if (x > y) x = y;}
      
      inline void Tarjan(int x)
      {
          dfn[x] = low[x] = ++tis; int y;
          stk[++top] = x; inv[x] = true;
          for (Edge *e = lst[x]; e; e = e->nxt)
              if (!dfn[y = e->to])
                  Tarjan(y), CkMin(low[x], low[y]);
              else if (inv[y])
                  CkMin(low[x], dfn[y]);
          if (dfn[x] == low[x])
          {
              inv[x] = false; col[x] = ++C;
              while (y = stk[top--], y != x) 
                  inv[y] = false, col[y] = C;
          }
      }
      
      int main()
      {
          n = get(); m = get(); K = get(); int x, y, w;
          while (m--)
          {
              x = get(); y = get();
              Link(x + n, y); Link(y + n, x);
          }
          for (int i = 1; i <= n; ++i)
              Link(i, i + n + n), Link(i + n + n + n, i + n); 
          while (K--)
          {
              w = get(); 
              for (int i = 1; i <= w; ++i) 
              {
                  a[i] = get();
                  b[i] = a[i] + n + n;
              }
              for (int i = 2; i <= w; ++i)
              {
                  Link(b[i - 1], b[i]); Link(b[i] + n, b[i - 1] + n);
                  Link(a[i], b[i - 1] + n); Link(b[i - 1], a[i] + n);
              }
          } 
          for (int i = 1, im = n << 2; i <= im; ++i)
              if (!dfn[i]) Tarjan(i);
          bool flag = false;
          for (int i = 1; i <= n; ++i)
              if (col[i] == col[i + n])
              {
                  flag = true;
                  break;
              }
          for (int i = 1; i <= n; ++i)
              if (col[i + n + n] == col[i + n + n + n])
              {
                  flag = true;
                  break;
              }
          puts(flag ? "NIE" : "TAK"); 
      } 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值