2 - sat

    · 博主语文体育老师教的

    · 本文年龄限定 16+

    · 吐槽以上 2条的都是⑨



    话说某天 ( 不就昨天么 ) 考试, 考到这样一道题 :

    一张 N * M 的地图上,有炮塔、敌人、障碍、空地四种地形 ( 谜之声 : 炮塔和敌人也算地形么? 语文怎么学的啊魂淡! )

    总之就是每个格子都是以上四种类型...

    每个炮塔可以选择四种方式攻击:向左、向下 ;  向左、向上 ;  向右、向下 ;  向右、向上. 

    并且炮塔的攻击是一条射线,  当且仅当遇到障碍或其他炮塔或是地图边界时, 射线才会终止. 炮塔攻击路径上的任何敌人都会被消灭.

    求一种炮塔的攻击方式, 使得炮塔不互相攻击, 并且能够消灭所有敌人.

    必定有解.


    模拟退火骗了70point.... 

    还以为是DLX 什么的搜索……结果题解怒D一记 : 2 - sat

    怒补……


    推荐:

    1、《2-sat》 (唐浩)

    2、《2-sat算法浅析》 (赵爽)

    3、《由对称性解2-sat问题》 (伍昱)

    4、白书   ( 以上3个瞬间坑爹了... )


    理论什么的就不说了……

    因为白书有的人很懒 ( 谜之声2 : 不就是你自己啊! ) 导致没买.

    所以就简单说说白书的算法.

    连边还是一样的方法,   如果选了 i 就必须选 j, 则连边 i -> j   

    只是不用缩点了. ( 噗...要[1]、[2]、[3] 何用)

    每次找一个没有被标记的变量, 尝试标记其为 true, 如果成功 (与之前没有冲突), 则继续标记下一个.

    如果失败, 则标记其为 false, 如果成功, 继续标记下一个.

    否则该 2 - sat 无解.

    这个算法在[3]、[4]中被承认是正确的, 不过[3]中说该算法时间复杂度为O(nm).

    怎么想也应该是 O (m) 啊= =、

    考虑算法的伪代码:

    procedure solve( z )

        if z has been marked  then

             if  z is false  then return false

            else  return true

        mark z true

        for each (z, u)

           if   !solve(u)  then return false;

       return true

    end procedure


    明显一个点最多被标记二次 ( 失败的话需要重新标记一次 )

    如此, 每条边会被访问二次, 那么时间复杂度应当是 O (m)

    这是坑爹么 ? = = ? 求解释......

    

    我竟然打了缩点……嗯练练代码能力还是没问题的…… ( 但是以后绝对不打缩点了= =、代码量多了1kb啊魂淡 )

    以下是非常挫的Code.......


    

#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;

char v[110][110];
bool t[40010], vis[40010], in[40010];
int dmp[40010], slf[40010], col[40010], pos[40010], paint[40010];
int n, m, mas, tower, top, sg, vs, set;
int q[40010], out[40010], bn[40010], size[40010], p[110][110];

struct edge
{
     int sg, c[200010], next[200010], g[40010];
     void add(int x, int y) { c[++sg] = y, next[sg] = g[x], g[x] = sg;  }
}  w, e, f;
void Right(int &x, int &y) {  y++; }
void Left(int &x, int &y)  {  y--; }
void Up(int &x, int &y)    {  x--; }
void Down(int &x, int &y)  {  x++; }
int get(int x, int y, void t(int &a, int &b))
{
    while (x  &&  x <= n  &&  y  &&  y <= m  &&  (v[x][y] == '.'  ||  v[x][y] == 'n'))  t(x, y);
    return p[x][y];
}
void compare(int a, int b, int c, int d)
{
     int mak = !a + !b + !c + !d;
     if (mak == 3)  
       {  
          if (a)  t[a + tower + mas] = true;
          if (b)  t[b + tower] = true;
          if (c)  t[c] = true; 
          if (d)  t[d + mas] = true;  return;
       }    
     if (a  &&  c)    e.add(a + tower + mas, c + mas),  e.add(c, a + tower); 
     if (b  &&  c)    e.add(b + tower, c + mas),  e.add(c, b + tower + mas);
     if (a  &&  d)    e.add(a + tower + mas, d),  e.add(d + mas, a + tower);
     if (b  &&  d)    e.add(b + tower, d),  e.add(d + mas, b + tower + mas);
}
void Topo()
{
     int head = 0;
     for (int i = 1; i <= sg; ++i)
       if (!out[i])  q[++top] = i;
     while (++head <= top)
       for (int x = w.g[q[head]]; x; x = w.next[x])
         if (!(--out[w.c[x]]))  q[++top] = w.c[x];  
}
void color(int p, int z)
{
     if (col[p])  return;  col[p] = z;
     for (int x = w.g[p]; x; x = w.next[x])  color(w.c[x], z);
}
int Print(int p)
{
     if (paint[p + mas] == 1  &&  paint[p + tower] == 1)   return 1;
     if (paint[p + mas] == 1  &&  paint[p + tower + mas] == 1)  return 2;
     if (paint[p] == 1  &&  paint[p + tower + mas] == 1)  return 3;
     if (paint[p] == 1  &&  paint[p + tower] == 1)  return 4;
}
void dfs(int z)
{
     vis[z] = true;  in[z] = true;  int p = ++vs;  bn[vs] = z;  dmp[z] = slf[z] = ++set;
     for (int x = e.g[z]; x; x = e.next[x])
       if (!vis[e.c[x]])  dfs(e.c[x]),  slf[z] = min(slf[z], slf[e.c[x]]);
       else  if (in[e.c[x]])  slf[z] = min(slf[z], dmp[e.c[x]]);
     if (dmp[z] == slf[z])
       for (++sg; vs >= p; --vs)
         size[sg]++, pos[bn[vs]] = sg, in[bn[vs]] = false;
}
void Init_Map()
{
     int right, left, up, down;
     scanf("%d %d", &n, &m);
     for (int i = 1; i <= n; ++i)
       for (int j = 1; j <= m; ++j)
         {
            while (scanf("%c", v[i] + j), v[i][j] != 'T'  &&  v[i][j] != '#'  &&  v[i][j] != '.'  &&  v[i][j] != 'n');
            if (v[i][j] == 'T')  p[i][j] = ++tower;  }
     mas = tower * 2;
     for (int i = 1; i <= n; ++i)
       for (int j = 1; j <= m; ++j)
         if (v[i][j] == 'n')
           {
               right = get(i, j + 1, Right);  left = get(i, j - 1, Left);
               up = get(i - 1, j, Up);  down = get(i + 1, j, Down);
               if  (right  &&  left)  t[right + tower + mas] = t[left + tower] = true, right = left = 0;  
               if  (up  &&  down)  t[up] = t[down + mas] = true, up = down = 0;
               compare(right, left, up, down);
           }
     for (int i = 1; i <= n; ++i)
       for (int j = 1; j <= m; ++j)
         if (v[i][j] == 'T')
           {
               int q = p[i][j];
               right = get(i, j + 1, Right);  left = get(i, j - 1, Left);
               up = get(i - 1, j, Up);  down = get(i + 1, j, Down);
               if (right)  t[q + tower + mas] = t[right + tower] = true;
               if (left)  t[q + tower] = t[left + tower + mas] = true;
               if (up)  t[q] = t[up + mas] = true;
               if (down)    t[q + mas] = t[down] = true;
           }
     for (int i = 1; i <= tower * 4; ++i)  if (!vis[i])  dfs(i);
     for (int i = 1; i <= tower * 4; ++i)
       for (int x = e.g[i]; x; x = e.next[x])
         if (pos[e.c[x]] != pos[i])  w.add(pos[e.c[x]], pos[i]), out[pos[i]]++;
     for (int i = 1; i <= tower * 2; ++i)  f.add(pos[i], pos[i + mas]), f.add(pos[i + mas], pos[i]);
     for (int i = 1; i <= tower * 4; ++i)  if (t[i])  color(pos[i], 2);
     Topo();  int ans = 0;
     for (int i = 1; i <= top; ++i)
       if (!col[q[i]])
         {
             col[q[i]] = 1;  ans += size[q[i]];
             for (int x = f.g[q[i]]; x; x = f.next[x])  color(f.c[x], 2); 
         }
     for (int i = 1; i <= tower * 4; ++i)  paint[i] = col[pos[i]];
     for (int i = 1; i <= n; printf("\n"), ++i)
       for (int j = 1; j <= m; ++j)
         if (v[i][j] != 'T')  printf("%c", v[i][j]);
         else  printf("%d", Print(p[i][j]));
}
int main()
{
    freopen("tower.in", "r", stdin);
    freopen("tower.out", "w", stdout);
    Init_Map();
}












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值