十二省联考2019 字符串问题

原题链接

不会\(\text{SAM}\)的蒟蒻只好用\(\text{SA}\)来水了。

先读懂题意(窝在读题意这儿花了快\(20\)min).

简单来说,是给你一个字符串,然后给你两个区间的集合\(A,B\),一共有\(m\)组\(A_i\)到\(B_j\)的支配关系。若\(A_i,B_j\)有支配关系,则从\(A_i\)到\(B_j\)连边;若\(B_j\)是\(A_i\)的前缀,则珂以从\(B_j\)连向\(A_i\)。每个\(A_i\)有权值,是\(A_i\)区间的长度。当你把上述的图建好后,她要你求图中的最长路。如果珂以无限长,输出-1

一看就懵逼的神仙字符串图论题

来肝这题的神仙们应该都知道最长路咋求吧?就是先拓扑排序一下,然后从后往前\(\text{dp}\)。这里就不讲了。

分析一下,这张图上一共的点数是不超过\(|A|+|B|\),即是线性的。但边数珂能高达\(m+|A|\cdot|B|\)。再仔细地分析一下,从\(A_i\)到\(B_j\)连的边数是\(m\),是线性的。问题就转化成了优化\(B_j\)向\(A_i\)连边的过程。

结合\(B_j\)是\(A_i\)的前缀,我们很容易想到用\(\text{SA}\)。我们找到\(B_j\)在\(\text{sa}\)上的位置,设为\(\text{pos}\)。\(B_j\)连向的所有\(A_i\)必须满足\(\text{lcp}\)是\(\ge|B_j|\)的,在\(\text{sa}\)上必须是一个区间,且包含\(\text{pos}\)。设这个区间为\([l,r]\),那么必须满足:\(\min\limits_{l+1\le i\le r}height_i\ge |B_j|\),且\(l\)尽量忘左,\(r\)尽量往右。找\(l,r\)的过程珂以通过在\(\text{height}\)数组上建出\(\text{ST}\)表之后二分求出。然后只要再用线段树优化建图就珂以了。

但这样有个问题:你不能保证在\([l,r]\)区间内的\(|A_i|\ge |B_j|\)。这样只能得到\(80\)分。

所以我们需要考虑更好的做法:用可持久化线段树优化建图。按照\(|A_i|\)从大到小建树,每个\(A_i\)只在第\(n-|A_i|\)棵可持久化线段树建树时加入。在可持久化线段树上,第\(i\)棵树上的点要向第\(i-1\)棵树上的对应点连边(除非没有),这样珂以保证每一次加入的\(B_j\)的长度\(\le |A_i|\)。具体细节就康代码吧。

总时间复杂度:\(O(N\log N)\),空间复杂度:\(O(N\log N)\)

代码:

// Code by H~$~C
#include <bits/stdc++.h>
using namespace std;

#ifndef LOCAL_JUDGE
static char _in_buf[100000], *_in_p1 = _in_buf, *_in_p2 = _in_buf;
#define gc (__builtin_expect(_in_p1 == _in_p2, 0) && (_in_p2 = (_in_p1 = _in_buf) +         fread(_in_buf, 1, 100000, stdin), _in_p1 == _in_p2) ? -1 : *_in_p1++)
#else
#define gc getchar()
#endif
inline int read() {
  register char ch = gc;
  register int x = 0;
  while (ch < 48 || ch > 57) ch = gc;
  while (ch > 47 && ch < 58) x = (x << 3) + (x << 1) + (ch ^ 48), ch = gc;
  return x;
}

static const int Maxn = 200005;
static const int Maxs = 5000005;

int n, na, nb, N, m;
char str[Maxn];
int la[Maxn], ra[Maxn], lb[Maxn], rb[Maxn];
vector<int> A[Maxn];
int ST[Maxn][20];

int wa[Maxn], wb[Maxn], wc[Maxn], _s[Maxn];
int sa[Maxn], rnk[Maxn], height[Maxn];
template<typename T>
void build_SA(T *ss, int n, int m) {
  register int *x = wa, *y = wb, *tmp;
  register int i, j, w;
  for (i = 1; i <= n; ++i) _s[i] = ss[i];
  for (i = 1; i <= n; ++i) x[i] = _s[i], y[i] = i;
  for (i = 1; i <= m; ++i) wc[i] = 0;
  for (i = 1; i <= n; ++i) wc[x[i]]++;
  for (i = 2; i <= m; ++i) wc[i] += wc[i - 1];
  for (i = n; i >= 1; --i) sa[wc[x[y[i]]]--] = y[i];
  for (w = 1; w <= n; w <<= 1) {
    register int tot = 0;
    for (i = n - w + 1; i <= n; ++i) y[++tot] = i;
    for (i = 1; i <= n; ++i) if (sa[i] > w) y[++tot] = sa[i] - w;
    for (i = 1; i <= m; ++i) wc[i] = 0;
    for (i = 1; i <= n; ++i) wc[x[i]]++;
    for (i = 2; i <= m; ++i) wc[i] += wc[i - 1];
    for (i = n; i >= 1; --i) sa[wc[x[y[i]]]--] = y[i];
    tmp = x, x = y, y = tmp, x[sa[1]] = 1, tot = 1;
    for (i = 2; i <= n; ++i)
      x[sa[i]] = ((y[sa[i]] == y[sa[i - 1]] && y[sa[i] + w] == y[sa[i - 1] + w]) ? tot : ++tot);
    if (tot == n) break; m = tot;
  }
  for (i = 1; i <= n; ++i) rnk[sa[i]] = i;
  for (i = 1, w = 0; i <= n; ++i) {
    if (rnk[i] == 1) continue;
    if (w) --w;
    int j = sa[rnk[i] - 1];
    while (i + w <= n && j + w <= n && _s[i + w] == _s[j + w]) w++;
    height[rnk[i]] = w;
  }
}
// sa[rank] = name, rnk[name] = rank
// height[i] = lcp(suffix(sa[i - 1]), suffix(sa[i]))

vector<int> g[Maxs];
int deg[Maxs], val[Maxs];
inline void clear_all(int u) { g[u].clear(), deg[u] = val[u] = 0; }
inline void add_edge(int u, int v) { g[u].push_back(v), deg[v]++; }

struct Node {
  int id;
  Node *l, *r;
  Node() { }
  Node(int id, Node *l, Node *r)
  : id(id), l(l), r(r) { }
} *root[Maxn], pool[Maxs], *cur_pointer = pool;
inline Node *newnode(int id, Node *l = NULL, Node *r = NULL) {
  return &(*++cur_pointer = Node(id, l, r));
}
int insert(Node *&p, int l, int r, int pos) {
  if (!p) {
    p = newnode(++N);
    clear_all(N);
  }
  else {
    p = newnode(p->id, p->l, p->r);
    clear_all(++N);
    add_edge(N, p->id);
    p->id = N;
  }
  if (l == r) return p->id;
  int mid = (l + r) >> 1, res;
  if (pos <= mid) res = insert(p->l, l, mid, pos);
  else res = insert(p->r, mid + 1, r, pos);
  if (p->l) add_edge(p->id, p->l->id);
  if (p->r) add_edge(p->id, p->r->id);
  return res;
}
void link_edge(Node *p, int l, int r, int L, int R, int u) {
  if (!p) return ;
  if (L == l && r == R) return add_edge(u, p->id);
  int mid = (l + r) >> 1;
  if (R <= mid) return link_edge(p->l, l, mid, L, R, u);
  if (L > mid) return link_edge(p->r, mid + 1, r, L, R, u);
  link_edge(p->l, l, mid, L, mid, u);
  link_edge(p->r, mid + 1, r, mid + 1, R, u);
}

int que[Maxs], qh, qe;
long long dp[Maxs];

void solve() {
  cur_pointer = pool;
  register int i, j;
  register char ch;
  while ((ch = gc) < 33);
  for (n = 0; ch > 32; ch = gc) str[++n] = ch;
  for (na = read(), i = 1; i <= na; ++i)
    la[i] = read(), ra[i] = read();
  for (nb = read(), i = 1; i <= nb; ++i)
    lb[i] = read(), rb[i] = read();
  N = na + nb;
  build_SA<char>(str, n, 128);
  for (i = 1; i <= n; ++i) ST[i][0] = height[i];
  for (j = 1; j < 20; ++j)
    for (i = 1; i + (1 << j) - 1 <= n; ++i)
      ST[i][j] = min(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);

  for (i = 1; i <= N; ++i) clear_all(i);
  for (m = read(), i = 1; i <= m; ++i) {
    int x = read(), y = read();
    add_edge(x, y + na);
  }
  for (i = 1; i <= n; ++i) A[i].clear();
  for (i = 1; i <= na; ++i) {
    val[i] = ra[i] - la[i] + 1;
    A[val[i]].push_back(i);
  }

  root[n + 1] = NULL;
  for (i = n; i >= 1; --i) {
    root[i] = root[i + 1];
    for (int &x: A[i]) {
      int id = insert(root[i], 1, n, rnk[la[x]]);
      add_edge(id, x);
    }
  }
  for (i = 1; i <= nb; ++i) {
    int l = rnk[lb[i]], r = rnk[lb[i]];
    int len = rb[i] - lb[i] + 1;
    /* get the leftest position */ {
      for (j = 0; l - (1 << j) + 1 >= 2; ++j);
      for (; ~--j; ) {
        if (l - (1 << j) + 1 >= 2 && ST[l - (1 << j) + 1][j] >= len) {
          l -= (1 << j);
        }
      }
    }
    /* get the rightest position */ {
      for (j = 0; r + (1 << j) <= n; ++j);
      for (; ~--j; ) {
        if (r + (1 << j) <= n && ST[r + 1][j] >= len) {
          r += (1 << j);
        }
      }
    }
    link_edge(root[len], 1, n, l, r, i + na);
  }

  qh = qe = 0;
  for (i = 1; i <= N; ++i) {
    if (!deg[i]) que[qe++] = i;
  }
  while (qh < qe) {
    int u = que[qh++];
    for (int &v: g[u]) {
      if (!--deg[v]) {
        que[qe++] = v;
      }
    }
  }
  if (qe != N) {
    puts("-1");
    return ;
  }

  for (i = N - 1; ~i; --i) {
    int u = que[i];
    dp[u] = 0;
    for (int &v: g[u]) {
      dp[u] = max(dp[u], dp[v]);
    }
    dp[u] += val[u];
  }
  printf("%lld\n", *max_element(dp + 1, dp + N + 1));
}

int main() {
  int tests = read();
  while (tests--) solve();
  return 0;
}

跑得炒鸡慢\(\ldots\)最大的点用了\(7.40\)秒

原文地址:https://www.cnblogs.com/libra9z/p/12388830.html

时间: 2024-10-07 18:35:46

十二省联考2019 字符串问题的相关文章

luogu P5284 [十二省联考2019]字符串问题

传送门 如果某个串\(b\)是\(a_j\)的前缀,并且\(a_i\)支配\(b\),那么\(a_i\)后面就可以放\(a_j\),所以如果把对应的图建出来,问题就是求最长链,如果有环就是无限长 说到前缀,我们可以把所有\(a\)串建一棵\(Trie\),然后某个\(a_i\)支配的\(b\)串节点对应的子树内的\(a_j\)串节点都可以连边\((i,j)\).不过每次建\(Trie\)太慢了,我们可以利用\(SAM\)建后缀树,然后每次相当于一个点给某个点子树内的点连边,所以后缀树上父亲向儿子

「十二省联考 2019」字符串问题

「十二省联考 2019」字符串问题 解题思路 傻逼题.. 考虑问题转化为一个A串向其支配的所有B串的后缀A串连边,如果有环答案 \(-1\) 否则是这个 \(\text{DAG}\) 上最长路径,直接建图是 \(n^2\) 的,考虑优化建图即可. 由于 \(A,B\) 都是原串的一个子串,那么对原串的反串建 SAM,一个子串的后缀就是其所在节点上比它长的串以及,其子树里的所有串. 首先将所有 \(A,B\) 串在 SAM上用倍增定位并新建节点,把SAM上每个节点拆成入点和出点,对于SAM每一个节

「十二省联考 2019」字符串问题 解题报告

「十二省联考 2019」字符串问题 当场就去世了,我这菜人改了一下午 考虑一个A,B之间的连边实际表示了两个A之间的有向边,然后把A的连边处理好,就转成了拓扑排序找环+最长链 但是边数很多,考虑优化连边 A,B之间的连边显然没法优化的,考虑一个B可以表示所有它的后缀A 把串反向建出SAM,然后一个B的后缀就是par树的子树 可以拿倍增定位 好了这题就没了 注意到一个事情,定位的点可能重复,于是对SAM拆点,每个点挂一个vector表示一个A或者B的点在SAM的这个位置 然后考虑如何连边 一个B所

[十二省联考2019]异或粽子(可持久化tire,堆)

[十二省联考2019]异或粽子(luogu) Description 题目描述 小粽是一个喜欢吃粽子的好孩子.今天她在家里自己做起了粽子. 小粽面前有 nn 种互不相同的粽子馅儿,小粽将它们摆放为了一排,并从左至右编号为 11 到 nn.第 ii 种馅儿具有一个非负整数的属性值 a_iai?.每种馅儿的数量都足够多,即小粽不会因为缺少原料而做不出想要的粽子.小粽准备用这些馅儿来做出 kk 个粽子. 小粽的做法是:选两个整数数 ll, rr,满足 1 \leqslant l \leqslant r

十二省联考 2019

T1 给一个序列,求前 $k$ 大区间异或和的和 $n \leq 500000,k \leq min(n^2,200000)$ sol: 超级钢琴 对每个 $i$,维护一个三元组 $(l,r,i)$ 表示左端点在 $[l,r]$,右端点在 $i$ 的区间异或最值,维护一个堆,按这个异或最值排序,每次将堆顶拿出来,分裂成最多两个区间,查一下异或最大值即可,区间异或最大值可以前缀和+可持久化 Trie 来解决 #include <bits/stdc++.h> #define LL long lon

#4349. 「十二省联考 2019」异或粽子

题意内存限制:1024 MiB时间限制:1500 ms小粽是一个喜欢吃粽子的好孩子.今天她在家里自己做起了粽子. 小粽面前有 $n$ 种互不相同的粽子馅儿,小粽将它们摆放为了一排,并从左至右编号为 $1$ 到 $n$.第 $i$ 种馅儿具有一个非负整数的属性值 $a_i$.每种馅儿的数量都足够多,即小粽不会因为缺少原料而做不出想要的粽子.小粽准备用这些馅儿来做出 $k$ 个粽子. 小粽的做法是:选两个整数数 $l,r$,满足 $1\le l\le r\le n$,将编号在 $[l,r]$ 范围内

[十二省联考2019]异或粽子 (可持久化01tire 堆)

/* 查询异或最大值的方法是前缀和一下, 在01trie上二分 那么我们可以对于n个位置每个地方先求出最大的数, 然后把n个信息扔到堆里, 当我们拿出某个位置的信息时, 将他去除当前最大后最大的信息插入到堆中 所以动态维护01trie就可以了 */ #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> #define mm

[十二省联考2019] 异或粽子 解题报告 (可持久化Trie+堆)

interlinkage: https://www.luogu.org/problemnew/show/P5283 description: solution: 显然有$O(n^2)$的做法,前缀和优化一下即可 正解做法是先确定一个右端点$r$,找到最优的$l$使得该区间的异或和最大,这个可以用可持久化$Trie$实现.不懂的话可以在我的博客里搜索 对每个点取出来后把答案放进一个堆里,显然当前的堆顶一定会对答案产生贡献 然后我们考虑每次取出的右端点,它依旧可能产生贡献.即上一次取的最优的$l$把

P5283 [十二省联考2019]异或粽子

传送门 超级钢琴+可持久化$Trie$ 同样设三元组 $(o,l,r)$ 表示左端点为 $o$,右端点 $\in [l,r]$ 的区间的最大异或值,这个东西可以用可持久化 $Trie$ 来维护 一开始把所有 $(i,i,n)$ 扔到堆里,然后每次取出计算贡献,设取得最大异或值的位置为 $t$,然后再把 $(o,l,t-1)$ 和 $(o,t+1,r)$ 扔到堆里 具体还是看代码,很容易理解 注意可能爆 $int$,所以要开 $unsigned\ int$,要注意代码常数,我代码 $luogu$