1. 程式人生 > 實用技巧 >「十二省聯考 2019」字串問題

「十二省聯考 2019」字串問題

知識點:SA,可持久化線段樹,優化建圖,DAGDP
原題面:LojLuogu

神筆出題人居然卡清空/jk
調一上午發現把昨天的 TLE 程式碼的鄰接表換成 vector 就過了/jk
草草草草草

簡述

\(T\) 組資料,每次給定一字串 \(S\)
\(S\) 中存在 \(n_a\) 個 A 類子串 \((la_i, ra_i)\)\(n_b\) 個 B 類子串 \((lb_i,rb_i)\)。且存在 \(m\) 組支配關係,支配關係 \((x,y)\) 表示第 \(x\) 個 A 類串支配第 \(y\) 個 B 類串。
要求構造一個目標串 \(T\),滿足:

  • \(T\) 由若干 A 類串拼接而成。
  • 對於分割中所有相鄰的串,後一個串存在一個被前一個串支配的字首。

求該目標串的最大長度,若目標串可以無限長輸出 \(-1\)
\(1\le T\le 100\)\(n_a,n_b,|S|,m\le 2\times 10^5\)
6S,1G。

分析

首先建立圖論模型,從每個 A 類子串向其支配的 B 串連邊,從每個 B 串向以它為字首的 A 串連邊。A 串節點的權值為其長度,B 串節點權值為 0。
在圖上 DP 求得最長路即為答案,若圖中存在環則無解。
第一類邊有 \(m\) 條,但第二類邊數可以達到 \(n_an_b\) 級別,考慮優化建圖。

對於某 A 串 \((la_i, ra_i)\)

,它以 B 串 \((lb_j, rb_j)\) 作為一個字首的充要條件是 \(\operatorname{lcp}(S[la_i:n],S[lb_j:n]) \ge rb_j-lb_j+1\)\(ra_i - la_i + 1\ge rb_j-lb_j+1\)
對於限制一,考慮求得 \(S\) 的 SA,對 \(\operatorname{height}\) 建立 ST 表,可在 \(sa\) 上二分求得滿足 \(\operatorname{lcp}\ge rb_j-lb_j+1\) 的區間的左右邊界,滿足條件的 A 串一定都在這段區間內。第二類邊轉化為區間連邊問題。
此時不考慮限制二直接線段樹優化建圖,可以拿到 80 分的好成績。

限制二實際上限定了 B 連邊的物件的長度。
考慮將所有 A,B 串按長度遞減排序,按長度遞減列舉 A 串並依次加入可持久化線段樹。
對於每一個 B 串,先找到使得 A 串長度大於其長度的最晚的歷史版本,此時線段樹上的所有 A 串長度都大於其長度,再向這棵線段樹上的節點連邊。

時間複雜度 \(O((|S| + n_a + n_b)\log n)\),空間複雜度 \(O(m + (|S| + n_a + n_b)\log n)\),不需要刻意卡常就能穩過。


看見上面輕描淡寫的是不是覺得這題太傻逼了?以下是菜雞 Lb 在程式碼實現上的小問題。

邊數在極限資料下可以達到 \(10^7\) 級別,不注意空間大小和清空時的實現會被卡到 60。這個時空限制顯然就是給選手亂搞的,陣列往大了開就行。

線上段樹優化建圖中,實點會在建樹操作中就與虛點連好邊。本題的實點是代表 A,B 串的節點,在本題的可持久化線段樹優化中,實點與虛點的連邊發生在動態開點的插入過程中。
在新建節點時,需要將該節點連向上一個版本中對應位置的節點。
對於 A 串 \((la_i, ra_i)\),它應該被插入到線段樹中 \(rk_{la_i}\) 的位置,即葉節點 \(rk_{la_i}\) 與該實點相連。

\(\operatorname{height}_1\) 沒有意義。

注意二分時的初始值。

long long

函式傳參順序 是通過棧傳遞的,因此是從右向左的,下面這段程式碼會輸出 cba

int f(int a, int b, int c) {
  return 0;
}
int main() {
  return  f(printf("a"),printf("b"),printf("c"));
  return 0;
}

程式碼

//知識點:SA,可持久化線段樹,優化建圖,DAGDP
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
struct Str {
  int l, r, lth, id;
} subs[kN << 1];
int node_num, n, na, nb, m, into[kN <<5];
int e_num, head[kN << 5], v[50 * kN], ne[50 * kN];
LL val[kN << 5], f[kN << 5];
char s[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(LL &fir_, LL sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
bool cmp(Str fir_, Str sec_) {
  if (fir_.lth != sec_.lth) return fir_.lth > sec_.lth;
  return fir_.id < sec_.id;
}
void Add(int u_, int v_) {
  v[++ e_num] = v_, ne[e_num] = head[u_], head[u_] = e_num;
  into[v_] ++;
}
namespace ST {
  int Minn[kN][21], Log2[kN];
  void MakeST(int *a_) {
    for (int i = 1; i <= n; ++ i) Minn[i][0] = a_[i];
    for (int i = 2; i <= n; ++ i) Log2[i] = Log2[i >> 1] + 1;
    for (int j = 1; j <= 20; ++ j) {
      for (int i = 1; i + (1 << j) - 1 <= n; ++ i) { //
        Minn[i][j] = std::min(Minn[i][j - 1], Minn[i + (1 << (j - 1))][j - 1]);
      }
    }
  }
  int Query(int l_, int r_) {
    int k = Log2[r_ - l_ + 1];
    return std::min(Minn[l_][k], Minn[r_ - (1 << k) + 1][k]);
  }
}
namespace SA {
  int sa[kN], rk[kN << 1];
  int oldrk[kN << 1], cnt[kN], id[kN];
  int height[kN];
  void SuffixSort() {
    int rknum = std::max(n, 300);
    memset(cnt, 0, sizeof (cnt));
    for (int i = 1; i <= n; ++ i) cnt[rk[i] = s[i]] ++;
    for (int i = 1; i <= rknum; ++ i) cnt[i] += cnt[i - 1];
    for (int i = n; i >= 1; -- i) sa[cnt[rk[i]] --] = i;
    
    for (int w = 1, p; w < n; w <<= 1) {
      p = 0;
      for (int i = n; i > n - w; -- i) id[++ p] = i;
      for (int i = 1; i <= n; ++ i) {
        if (sa[i] > w) id[++ p] = sa[i] - w;
      }
      
      memset(cnt, 0, sizeof (cnt));
      for (int i = 1; i <= n; ++ i) ++ cnt[rk[id[i]]];
      for (int i = 1; i <= rknum; ++ i) cnt[i] += cnt[i - 1];
      for (int i = n; i >= 1; -- i) sa[cnt[rk[id[i]]] --] = id[i];
      
      for (int i = 1; i <= n; ++ i) oldrk[i] = rk[i];
      rknum = 0;
      for (int i = 1; i <= n; ++ i) {
        rknum += (oldrk[sa[i]] == oldrk[sa[i - 1]] && 
                  oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) ^ 1;
        rk[sa[i]] = rknum;
      }
    }
  }
  void GetHeight() {
    for (int i = 1, k = 0; i <= n; ++ i) {
      if (rk[i] == 1) {
        k = 0;
      } else {
        if (k) -- k;
        int j = sa[rk[i] - 1];
        while (i + k <= n && j + k <= n && 
               s[i + k] == s[j + k]) {
          ++ k;
        }
      }
      height[rk[i]] = k;
    }
  }
  int Lcp(int x_, int y_) {
    if (x_ > y_) std::swap(x_, y_);
    return ST::Query(x_ + 1, y_);
  }
  void Init() {
    SuffixSort();
    GetHeight();
    ST::MakeST(SA::height);
  }
}
namespace Hjt {
  #define ls lson[now_]
  #define rs rson[now_]
  #define mid ((L_+R_)>>1)
  int root[kN], lson[kN << 5], rson[kN << 5];
  void Insert(int &now_, int pre_, int L_, int R_, int pos_, int id_) {
    now_ = ++ node_num;
    ls = lson[pre_], rs = rson[pre_];
    if (pre_) Add(now_, pre_);
    if (L_ == R_) {
      Add(now_, id_);
      return ;
    }
    if (pos_ <= mid) {
      Insert(ls, lson[pre_], L_, mid, pos_, id_);
      Add(now_, ls);
    } else {
      Insert(rs, rson[pre_], mid + 1, R_, pos_, id_);
      Add(now_, rs);
    }
  }
  void AddEdge(int now_, int L_, int R_, int l_, int r_, int id_) {
    if (! now_) return ;
    if (l_ <= L_ && R_ <= r_) {
      Add(id_, now_);
      return ;
    }
    if (l_ <= mid) AddEdge(ls, L_, mid, l_, r_, id_);
    if (r_ > mid) AddEdge(rs, mid + 1, R_, l_, r_, id_);
  }
  #undef ls
  #undef rs
  #undef mid
}
void Init() {
  e_num = 0;
  for (int i = 0; i <= node_num; ++ i) {
    head[i] = val[i] = into[i] = f[i] = 0;
  }
  
  scanf("%s", s + 1);
  n = strlen(s + 1);
  SA::Init();
  na = read();
  for (int i = 1; i <= na; ++ i) {
    int l_ = read(), r_ = read();
    subs[i] = (Str) {l_, r_, r_ - l_ + 1, i};
    val[i] = subs[i].lth;
  }
  nb = read();
  for (int i = 1; i <= nb; ++ i) {
    int l_ = read(), r_ = read();
    subs[na + i] = (Str) {l_, r_, r_ - l_ + 1, na + i};
  }
  m = read();
  for (int i = 1; i <= m; ++ i) {
    int u_ = read(), v_ = read();
    Add(u_, v_ + na); //Add(read(), read()+na) 會倒著讀
  }
  node_num = na + nb;
}
bool Check(int x_, int y_, int lth_) {
  return SA::Lcp(x_, y_) >= lth_;
}
void AddEdgeB(int id_, int now_) {
  int pos = SA::rk[subs[id_].l], l_ = pos, r_ = pos; //l_,r_ 初始值
  for (int l = 1, r = pos - 1; l <= r; ) {
    int mid = (l + r) >> 1;
    if (Check(mid, pos, subs[id_].lth)) {
      r = mid - 1;
      l_ = mid;
    } else {
      l = mid + 1;
    }
  }
  for (int l = pos + 1, r = n; l <= r; ) {
    int mid = (l + r) >> 1;
    if (Check(pos, mid, subs[id_].lth)) {
      l = mid + 1;
      r_ = mid;
    } else {
      r = mid - 1; 
    }
  }
  Hjt::AddEdge(Hjt::root[now_], 1, n, l_, r_, subs[id_].id);
}
void Build() {
  node_num = na + nb;
  std::sort(subs + 1, subs + na + nb + 1, cmp);
  for (int now = 0, i = 1; i <= na + nb; ++ i) {
    if (subs[i].id > na) {
      AddEdgeB(i, now);
      continue;
    }
    ++ now;
    Hjt::Insert(Hjt::root[now], Hjt::root[now - 1], 1, n, SA::rk[subs[i].l], 
                subs[i].id);
  }
}
void TopSort() {
  std::queue <int> q;
  for (int i = 1; i <= node_num; ++ i) {
    if (!into[i]) {
      f[i] = val[i];
      q.push(i);
    }
  }
  while (! q.empty()) {
    int u_ = q.front(); q.pop();
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      Chkmax(f[v_], f[u_] + val[v_]);
      -- into[v_];
      if (!into[v_]) q.push(v_);
    }
  }
  LL ans = 0;
  for (int i = 1; i <= node_num; ++ i) {
    Chkmax(ans, f[i]);
    if (into[i]) {
      printf("-1\n");
      return ;
    }
  }
  printf("%lld\n", ans);
}
//=============================================================
int main() {
  int T = read();
  while (T --) {
    Init();
    Build();
    TopSort();
  }
  return 0;
}