1. 程式人生 > >[UOJ317]【NOI2017】遊戲 題解

[UOJ317]【NOI2017】遊戲 題解

  • 題意:

    ​ 小 L 計劃進行 \(n\) 場遊戲,每場遊戲使用一張地圖,小 L 會選擇一輛車在該地圖上完成遊戲。

    ​ 小 L 的賽車有三輛,分別用大寫字母 A、B、C 表示。地圖一共有四種,分別用小寫字母 x、a、b、c 表示。其中,賽車 A 不適合在地圖 a 上使用,賽車 B 不適合在地圖 b 上使用,賽車 C 不適合在地圖 c 上使用,而地圖 x 則適合所有賽車參加。適合所有賽車參加的地圖並不多見,最多隻會有 \(d\) 張。

    \(n\) 場遊戲的地圖可以用一個小寫字母組成的字串描述。例如:\(\underline{S=xaabxcbc}\) 表示小 L 計劃進行 8 場遊戲,其中第 1 場和第 5 場的地圖型別是 x,適合所有賽車,第 2 場和第 3 場的地圖是 a,不適合賽車 A,第 4 場和第 7 場的地圖是 b,不適合賽車 B,第 6 場和第 8 場的地圖是 c,不適合賽車 C。

    ​ 小 L 對遊戲有一些特殊的要求(共 \(m\) 條),這些要求可以用四元組 \((i,h_i,j,h_j)\) 來描述,表示若在第 \(i\) 場使用型號為 \(h_i\) 的車子,則第 \(j\) 場遊戲要使用型號為 \(h_j\) 的車子。

    ​ 你能幫小 L 選擇每場遊戲使用的賽車嗎?如果有多種方案,輸出任意一種方案。如果無解,輸出 “-1”(不含雙引號)。

  • 資料範圍:

    \(n \le 50000,d \le 8,m\le100000.\)

  • 題解:

    話說我NOI的題改起來好費力啊TAT 一道就一天(還對著資料改)

    不難發現這和之前一道題類似LA 3713

    雖然有很多種類,但每個人也只有兩種可以選擇,並且關係也是二元的。

    所以我們就可以套2-SAT模板了qwq 不會2-SAT請點這裡.

    首先暴力列舉每個 \(x\) 的狀態,從 \(a,b,c\) 中選取一個就行了。

    然後對於那些二元關係 \((i,h_i,j,h_j)\),就如下考慮。

    1. \(i\) 可以選擇 \(h_i\) 這種賽車
      1. \(j\) 無法選擇 \(h_j\) 這種賽車,那麼 \(i\) 就必不能選 \(h_i\) 了,就連一條 \(h_i \to \lnot h_i\) 的邊(此處 \(\lnot h_i\) 表示 \(i\) 可以選擇的另一種賽車)
      2. \(j\) 可以選擇 \(h_j\) 這種賽車,那麼就只要連 \(h_i \to h_j\)
        的邊就好了
    2. \(i\) 不可以選擇 \(h_i\) 這種賽車:那就直接跳過就行了。

    然後記得要建逆否命題等價的另一條邊。

    如果用DFS複雜度為 \(\Theta(3^d nm)\)Tarjan就是 \(\Theta(3^d (n+m))\) 啦。

    其實第二個Tarjan複雜度已經足夠過了。。。但是能更優!

    有這樣一個結論:

    如果一個 \(x\) 已經考慮過 \(a,b\) 兩個狀態了,那麼我們就不用考慮為 \(c\) 的狀態。

    這是為什麼呢?

    因為 \((A,B)\)\((B,C)\) 的兩種選擇你都已經考慮過了,那麼剩下一種 \((A,C)\) 你前面一定會在前面一種選過。(因為每個點只能從那三種中選擇一種賽車,如果另外兩種都不可行,剩下一種也不行了)

    這樣我們就可以將複雜度降為 \(\Theta(2^d(n+m))\) qwq

    然而又有垃圾UOJ hack資料 我判了一下快到0.8s的時候,直接輸出"-1"...才過 (卡常數真的噁心

  • 程式碼:

    我一開始寫了個DFS的可以跑過85分.... 然而有人能用這個A掉 Orz

  #include <bits/stdc++.h>
  #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
  #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
  #define Set(a, v) memset(a, v, sizeof(a))
  using namespace std;

  inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
  inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}

  inline int read() {
      int x = 0, fh = 1; char ch = getchar();
      for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
      for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
      return x * fh;
  }

  void File() {
  #ifdef zjp_shadow
    freopen ("317.in", "r", stdin);
    freopen ("317.out", "w", stdout);
  #endif
  }

  const int N = 100010;
  char str[N];
  int Jump[N], len, Last;

  int m;
  struct Limit { int u, v; char colu, colv; } lt[N];

  char opt[N][2];

  struct Two_SAT {
    bitset<N> mark;
    int Next[N], Head[N], to[N], e, n;

    void Init(int n) { this -> n = n; mark.reset(); Set(Head, 0); e = 0; }

    void add_edge(int u, int v) { to[++ e] = v; Next[e] = Head[u]; Head[u] = e; }

    void Add(int x, int xv, int y, int yv) {
        x = x << 1 | xv; y = y << 1 | yv;
        add_edge(x, y); add_edge(y ^ 1, x ^ 1);
    } //(x,xv) -> (y,yv)

    int sta[N], top;
    bool Dfs(int x) {
        if (mark[x ^ 1]) return false;
        if (mark[x]) return true;
        sta[++ top] = x; mark[x] = true;
        for (int i = Head[x]; i; i = Next[i])
            if (!Dfs(to[i])) return false;
        return true;
    }

    bool Solve() {
        for (int i = 2; i <= 2 * n; i += 2)
            if (!mark[i] && !mark[i ^ 1]) {
                top = 0;
                if (!Dfs(i)) {
                    while (top) mark[sta[top --]] = false;
                    if (!Dfs(i ^ 1)) { return false; }
                }
            }
        return true;
    }

  } T;
  int n;

  inline bool Check() {
    T.Init(n);
    For (i, 1, m) {
        int u = lt[i].u, v = lt[i].v, colu = lt[i].colu, colv = lt[i].colv;
        if (u == v && colu == colv) continue ;
        For (v1, 0, 1) if (opt[u][v1] == colu) {
            bool flag = false;
            For (v2, 0, 1) if (opt[v][v2] == colv) { flag = true; T.Add(u, v1, v, v2); }
            if (!flag) 
                T.Add(u, v1, u, v1 ^ 1);
        }
    }
    return T.Solve();
  }

  inline void Out() {
    For (i, 1, n) For (j, 0, 1) if (T.mark[i << 1 | j]) putchar(toupper(opt[i][j]));
  }

  void Dfs(int pos) {
    if (pos == len + 1) { if (Check()) { Out(); exit(0); } return ; }
    opt[pos][0] = 'b'; opt[pos][1] = 'c'; str[pos] = 'a'; Dfs(Jump[pos]);
    opt[pos][0] = 'a'; opt[pos][1] = 'c'; str[pos] = 'b'; Dfs(Jump[pos]);
  }

  int main () {
    File();
    n = len = read(); read();
    scanf ("%s", str + 1);
    Last = len + 1;
    Fordown(i, len, 1) 
        if (str[i] == 'x') { Jump[i] = Last; Last = i; }
        else { int cnt = 0; For (j, 0, 2) if (str[i] != 'a' + j) { opt[i][cnt ++] = 'a' + j; } }
    m = read();
    For (i, 1, m) {
        int u, v; char colu, colv;
        scanf ("%d %c %d %c", &u, &colu, &v, &colv);
        lt[i].u = u; lt[i].v = v; lt[i].colu = tolower(colu); lt[i].colv = tolower(colv);
    }
    
    Dfs(Last);
    puts("-1");
      return 0;
  }

正解就用Tarjan縮點就行咯qwq

  #include <bits/stdc++.h>
  #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
  #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
  #define Set(a, v) memset(a, v, sizeof(a))
  using namespace std;

  inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
  inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}

  inline int read() {
      int x = 0, fh = 1; char ch = getchar();
      for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
      for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
      return x * fh;
  }

  void File() {
  #ifdef zjp_shadow
    freopen ("317.in", "r", stdin);
    freopen ("317.out", "w", stdout);
  #endif
  }

  const int N = 200100;
  char str[N];
  int Jump[N], len, Last;

  int m;
  struct Limit { int u, v; char colu, colv; } lt[N];

  char opt[N][2];

  struct Two_SAT {
    int Next[N], Head[N], to[N], e, n;

    void Init(int n) { this -> n = n; For(i, 2, n << 1 | 1) Head[i] = 0; e = 0; }

    void add_edge(int u, int v) { to[++ e] = v; Next[e] = Head[u]; Head[u] = e; }

    void Add(int x, int xv, int y, int yv) {
        x = x << 1 | xv; y = y << 1 | yv;
        add_edge(x, y); add_edge(y ^ 1, x ^ 1);
    } //(x,xv) -> (y,yv)

    int sccno[N], scc_cnt;
    int dfn[N], lowlink[N];
    int sta[N], top, clk;
    void Tarjan(int u) {
        lowlink[u] = dfn[u] = ++clk; sta[++ top] = u;
        for (int i = Head[u]; i; i = Next[i]) {
            int v = to[i];
            if (!dfn[v]) { Tarjan(v); chkmin(lowlink[u], lowlink[v]); }
            else if (!sccno[v]) chkmin(lowlink[u], dfn[v]);
        }
        if (lowlink[u] == dfn[u]) {
            ++ scc_cnt; for (;;) { int now = sta[top --]; sccno[now] = scc_cnt; if (now == u) break ; } 
        }
    }

    bool Solve() {
        For (i, 2, n << 1 | 1) dfn[i] = sccno[i] = 0; scc_cnt = clk = 0;
        For (i, 2, n << 1 | 1) if (!dfn[i]) Tarjan(i);
        For (i, 1, n) 
            if (sccno[i << 1] == sccno[i << 1 | 1]) return false;
        return true;
    }

    void Out() {
        For (i, 1, n) { putchar(toupper(opt[i][sccno[i << 1] > sccno[i << 1 | 1]])) ; }
    }
  } T;
  int n;

  int cnt = 0;
  inline bool Check() {
    T.Init(n);
    For (i, 1, m) {
        int u = lt[i].u, v = lt[i].v, colu = lt[i].colu, colv = lt[i].colv;
        if (u == v && colu == colv) continue ;
        For (v1, 0, 1) if (opt[u][v1] == colu) {
            bool flag = false;
            For (v2, 0, 1) if (opt[v][v2] == colv) { flag = true; T.Add(u, v1, v, v2); }
            if (!flag) T.Add(u, v1, u, v1 ^ 1);
        }
    }
    int res = T.Solve();
    return res;
  }

  void Dfs(int pos) {
    if ((double) clock() / CLOCKS_PER_SEC >= 0.8) { puts("-1"); exit(0); }
    if (pos == len + 1) { if (Check()) { T.Out(); exit(0); } return ; }
    opt[pos][0] = 'a'; opt[pos][1] = 'c'; str[pos] = 'b'; Dfs(Jump[pos]);
    opt[pos][0] = 'b'; opt[pos][1] = 'c'; str[pos] = 'a'; Dfs(Jump[pos]);
  }

  int main () {
    File();
    n = len = read(); read(); scanf ("%s", str + 1); Last = len + 1;
    Fordown(i, len, 1) 
        if (str[i] == 'x') { Jump[i] = Last; Last = i; }
        else { int cnt = 0; For (j, 0, 2) if (str[i] != 'a' + j) { opt[i][cnt ++] = 'a' + j; } }
    m = read();
    For (i, 1, m) {
        int u, v; char colu, colv; scanf ("%d %c %d %c", &u, &colu, &v, &colv);
        lt[i].u = u; lt[i].v = v; lt[i].colu = tolower(colu); lt[i].colv = tolower(colv);
    }
    Dfs(Last); puts("-1");
      return 0;
  }