OSDN Git Service

:art: 修改例题格式
authorouuan <y___o___u@126.com>
Sun, 15 Sep 2019 03:27:19 +0000 (11:27 +0800)
committerouuan <y___o___u@126.com>
Sun, 15 Sep 2019 03:27:19 +0000 (11:27 +0800)
docs/string/hash.md
docs/string/seq-automaton.md
docs/string/trie.md

index 0cd1713..43ac5d8 100644 (file)
@@ -87,13 +87,16 @@ void cmp(const string& s, const string& t) {
 ### 例题
 
 ???+note "[CF1200E Compress Words](http://codeforces.com/contest/1200/problem/E)"
-    题目大意:给你若干个字符串,答案串初始为空。第 $i$ 步将第 $i$ 个字符串加到答案串的后面,但是尽量地去掉重复部分(即去掉一个最长的、是原答案串的后缀、也是第 $i$ 个串的前缀的字符串),求最后得到的字符串。
+    给你若干个字符串,答案串初始为空。第 $i$ 步将第 $i$ 个字符串加到答案串的后面,但是尽量地去掉重复部分(即去掉一个最长的、是原答案串的后缀、也是第 $i$ 个串的前缀的字符串),求最后得到的字符串。
 
-    每次需要求最长的、是原答案串的后缀、也是第 $i$ 个串的前缀的字符串。枚举这个串的长度,哈希比较即可。
+    字符串个数不超过 $10^5$,总长不超过 $10^6$。
+    
+    ??? mdui-shadow-6 "题解"
+        每次需要求最长的、是原答案串的后缀、也是第 $i$ 个串的前缀的字符串。枚举这个串的长度,哈希比较即可。
 
-    当然,这道题也可以使用 [KMP 算法](./kmp.md) 解决。
+        当然,这道题也可以使用 [KMP 算法](./kmp.md) 解决。
 
-    ??? note "参考代码"
+    ??? mdui-shadow-6 "参考代码"
         ```cpp
         #include <algorithm>
         #include <cstdio>
index 6330a3e..74b2eab 100644 (file)
@@ -45,144 +45,154 @@ $$
 
 ## 例题
 
-###  [「HEOI2015」最短不公共子串](https://www.luogu.org/problem/P4112) 
-
-这题的 (1) 和 (3) 两问需要后缀自动机,而且做法类似,在这里只讲解 (2) 和 (4) 两问。
-
-(2) 比较简单,枚举 A 的子串输入进 B 的序列自动机,若不接受则计入答案。
-
-(4) 需要 DP。令 $f(i, j)$ 表示在 A 的序列自动机中处于状态 $i$ ,在 B 的序列自动机中处于状态 $j$ ,需要再添加多少个字符能够不是公共子序列。
-
- $f(i, null)=0$ 
-
- $f(i, j)=\min\limits_{\delta_A(i,c)\ne null}f(\delta_A(i, c), \delta_B(j, c))$ 
-
-??? note "整道题的参考代码"
-    ```cpp
-    #include <algorithm>
-    #include <cstdio>
-    #include <cstring>
-    #include <iostream>
-    
-    using namespace std;
-    
-    const int N = 2005;
-    
-    char s[N], t[N];
-    int na[N][26], nb[N][26], nxt[26];
-    int n, m, a[N], b[N], tot = 1, p = 1, f[N][N << 1];
-    
-    struct SAM {
-      int par, ch[26], len;
-    } sam[N << 1];
-    
-    void insert(int x) {
-      int np = ++tot;
-      sam[np].len = sam[p].len + 1;
-      while (p && !sam[p].ch[x]) {
-        sam[p].ch[x] = np;
-        p = sam[p].par;
-      }
-      if (p == 0)
-        sam[np].par = 1;
-      else {
-        int q = sam[p].ch[x];
-        if (sam[q].len == sam[p].len + 1)
-          sam[np].par = q;
-        else {
-          int nq = ++tot;
-          sam[nq].len = sam[p].len + 1;
-          memcpy(sam[nq].ch, sam[q].ch, sizeof(sam[q].ch));
-          sam[nq].par = sam[q].par;
-          sam[q].par = sam[np].par = nq;
-          while (p && sam[p].ch[x] == q) {
-            sam[p].ch[x] = nq;
+???+note "[「HEOI2015」最短不公共子串](https://www.luogu.org/problem/P4112)?
+
+    给你两个由小写英文字母组成的串 $A$ 和 $B$,求:
+
+    1. $A$ 的一个最短的子串,它不是 $B$ 的子串;
+    2. $A$ 的一个最短的子串,它不是 $B$ 的子序列;
+    3. $A$ 的一个最短的子序列,它不是 $B$ 的子串;
+    4. $A$ 的一个最短的子序列,它不是 $B$ 的子序列。
+
+    $1\le |A|, |B|\le 2000$。
+
+    ??? mdui-shadow-6 "题解"
+        这题的 (1) 和 (3) 两问需要后缀自动机,而且做法类似,在这里只讲解 (2) 和 (4) 两问。
+
+        (2) 比较简单,枚举 A 的子串输入进 B 的序列自动机,若不接受则计入答案。
+
+        (4) 需要 DP。令 $f(i, j)$ 表示在 A 的序列自动机中处于状态 $i$ ,在 B 的序列自动机中处于状态 $j$ ,需要再添加多少个字符能够不是公共子序列。
+
+        $f(i, null)=0$ 
+
+        $f(i, j)=\min\limits_{\delta_A(i,c)\ne null}f(\delta_A(i, c), \delta_B(j, c))$ 
+
+    ??? mdui-shadow-6 "参考代码"
+        ```cpp
+        #include <algorithm>
+        #include <cstdio>
+        #include <cstring>
+        #include <iostream>
+        
+        using namespace std;
+        
+        const int N = 2005;
+        
+        char s[N], t[N];
+        int na[N][26], nb[N][26], nxt[26];
+        int n, m, a[N], b[N], tot = 1, p = 1, f[N][N << 1];
+        
+        struct SAM {
+          int par, ch[26], len;
+        } sam[N << 1];
+        
+        void insert(int x) {
+          int np = ++tot;
+          sam[np].len = sam[p].len + 1;
+          while (p && !sam[p].ch[x]) {
+            sam[p].ch[x] = np;
             p = sam[p].par;
           }
-        }
-      }
-      p = np;
-    }
-    
-    int main() {
-      scanf("%s%s", s + 1, t + 1);
-    
-      n = strlen(s + 1);
-      m = strlen(t + 1);
-    
-      for (int i = 1; i <= n; ++i) a[i] = s[i] - 'a';
-      for (int i = 1; i <= m; ++i) b[i] = t[i] - 'a';
-    
-      for (int i = 1; i <= m; ++i) insert(b[i]);
-    
-      for (int i = 0; i < 26; ++i) nxt[i] = n + 1;
-      for (int i = n; i >= 0; --i) {
-        memcpy(na[i], nxt, sizeof(nxt));
-        nxt[a[i]] = i;
-      }
-    
-      for (int i = 0; i < 26; ++i) nxt[i] = m + 1;
-      for (int i = m; i >= 0; --i) {
-        memcpy(nb[i], nxt, sizeof(nxt));
-        nxt[b[i]] = i;
-      }
-    
-      int ans = N;
-    
-      for (int l = 1; l <= n; ++l) {
-        for (int r = l, u = 1; r <= n; ++r) {
-          u = sam[u].ch[a[r]];
-          if (!u) {
-            ans = min(ans, r - l + 1);
-            break;
+          if (p == 0)
+            sam[np].par = 1;
+          else {
+            int q = sam[p].ch[x];
+            if (sam[q].len == sam[p].len + 1)
+              sam[np].par = q;
+            else {
+              int nq = ++tot;
+              sam[nq].len = sam[p].len + 1;
+              memcpy(sam[nq].ch, sam[q].ch, sizeof(sam[q].ch));
+              sam[nq].par = sam[q].par;
+              sam[q].par = sam[np].par = nq;
+              while (p && sam[p].ch[x] == q) {
+                sam[p].ch[x] = nq;
+                p = sam[p].par;
+              }
+            }
           }
+          p = np;
         }
-      }
-    
-      printf("%d\n", ans == N ? -1 : ans);
-    
-      ans = N;
-    
-      for (int l = 1; l <= n; ++l) {
-        for (int r = l, u = 0; r <= n; ++r) {
-          u = nb[u][a[r]];
-          if (u == m + 1) {
-            ans = min(ans, r - l + 1);
-            break;
+        
+        int main() {
+          scanf("%s%s", s + 1, t + 1);
+        
+          n = strlen(s + 1);
+          m = strlen(t + 1);
+        
+          for (int i = 1; i <= n; ++i) a[i] = s[i] - 'a';
+          for (int i = 1; i <= m; ++i) b[i] = t[i] - 'a';
+        
+          for (int i = 1; i <= m; ++i) insert(b[i]);
+        
+          for (int i = 0; i < 26; ++i) nxt[i] = n + 1;
+          for (int i = n; i >= 0; --i) {
+            memcpy(na[i], nxt, sizeof(nxt));
+            nxt[a[i]] = i;
           }
-        }
-      }
-    
-      printf("%d\n", ans == N ? -1 : ans);
-    
-      for (int i = n; i >= 0; --i) {
-        for (int j = 1; j <= tot; ++j) {
-          f[i][j] = N;
-          for (int c = 0; c < 26; ++c) {
-            int u = na[i][c];
-            int v = sam[j].ch[c];
-            if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
+        
+          for (int i = 0; i < 26; ++i) nxt[i] = m + 1;
+          for (int i = m; i >= 0; --i) {
+            memcpy(nb[i], nxt, sizeof(nxt));
+            nxt[b[i]] = i;
           }
-        }
-      }
-    
-      printf("%d\n", f[0][1] == N ? -1 : f[0][1]);
-    
-      memset(f, 0, sizeof(f));
-    
-      for (int i = n; i >= 0; --i) {
-        for (int j = 0; j <= m; ++j) {
-          f[i][j] = N;
-          for (int c = 0; c < 26; ++c) {
-            int u = na[i][c];
-            int v = nb[j][c];
-            if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
+        
+          int ans = N;
+        
+          for (int l = 1; l <= n; ++l) {
+            for (int r = l, u = 1; r <= n; ++r) {
+              u = sam[u].ch[a[r]];
+              if (!u) {
+                ans = min(ans, r - l + 1);
+                break;
+              }
+            }
+          }
+        
+          printf("%d\n", ans == N ? -1 : ans);
+        
+          ans = N;
+        
+          for (int l = 1; l <= n; ++l) {
+            for (int r = l, u = 0; r <= n; ++r) {
+              u = nb[u][a[r]];
+              if (u == m + 1) {
+                ans = min(ans, r - l + 1);
+                break;
+              }
+            }
+          }
+        
+          printf("%d\n", ans == N ? -1 : ans);
+        
+          for (int i = n; i >= 0; --i) {
+            for (int j = 1; j <= tot; ++j) {
+              f[i][j] = N;
+              for (int c = 0; c < 26; ++c) {
+                int u = na[i][c];
+                int v = sam[j].ch[c];
+                if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
+              }
+            }
+          }
+        
+          printf("%d\n", f[0][1] == N ? -1 : f[0][1]);
+        
+          memset(f, 0, sizeof(f));
+        
+          for (int i = n; i >= 0; --i) {
+            for (int j = 0; j <= m; ++j) {
+              f[i][j] = N;
+              for (int c = 0; c < 26; ++c) {
+                int u = na[i][c];
+                int v = nb[j][c];
+                if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
+              }
+            }
           }
+        
+          printf("%d\n", f[0][0] == N ? -1 : f[0][0]);
+        
+          return 0;
         }
-      }
-    
-      printf("%d\n", f[0][0] == N ? -1 : f[0][0]);
-    
-      return 0;
-    }
-    ```
+        ```
index f987479..7d17b25 100644 (file)
@@ -48,55 +48,59 @@ struct trie {
 
 字典树最基础的应用——查找一个字符串是否在“字典”中出现过。
 
-####  [于是他错误的点名开始了](https://www.luogu.org/problemnew/show/P2580) 
-
-对所有名字建 Trie,再在 Trie 中查询字符串是否存在,第一次点名时标记为点过名。
-
-??? note "参考代码"
-    ```cpp
-    #include <cstdio>
-    
-    const int N = 500010;
-    
-    char s[60];
-    int n, m, ch[N][26], tag[N], tot = 1;
-    
-    int main() {
-      scanf("%d", &n);
-    
-      for (int i = 1; i <= n; ++i) {
-        scanf("%s", s + 1);
-        int u = 1;
-        for (int j = 1; s[j]; ++j) {
-          int c = s[j] - 'a';
-          if (!ch[u][c]) ch[u][c] = ++tot;
-          u = ch[u][c];
+???+note "[于是他错误的点名开始了](https://www.luogu.org/problemnew/show/P2580)"
+    给你 $n$ 个名字串,然后进行 $m$ 次点名,每次你需要回答“名字不存在”、“第一次点到这个名字”、“已经点过这个名字”之一。
+
+    $1\le n\le 10^4$, $1\le m\le 10^5$,所有字符串长度不超过 $50$。
+
+    ??? mdui-shadow-6 "题解"
+        对所有名字建 Trie,再在 Trie 中查询字符串是否存在、是否已经点过名,第一次点名时标记为点过名。
+
+    ??? mdui-shadow-6 "参考代码"
+        ```cpp
+        #include <cstdio>
+        
+        const int N = 500010;
+        
+        char s[60];
+        int n, m, ch[N][26], tag[N], tot = 1;
+        
+        int main() {
+          scanf("%d", &n);
+        
+          for (int i = 1; i <= n; ++i) {
+            scanf("%s", s + 1);
+            int u = 1;
+            for (int j = 1; s[j]; ++j) {
+              int c = s[j] - 'a';
+              if (!ch[u][c]) ch[u][c] = ++tot;
+              u = ch[u][c];
+            }
+            tag[u] = 1;
+          }
+        
+          scanf("%d", &m);
+        
+          while (m--) {
+            scanf("%s", s + 1);
+            int u = 1;
+            for (int j = 1; s[j]; ++j) {
+              int c = s[j] - 'a';
+              u = ch[u][c];
+              if (!u) break;  // 不存在对应字符的出边说明名字不存在
+            }
+            if (tag[u] == 1) {
+              tag[u] = 2;
+              puts("OK");
+            } else if (tag[u] == 2)
+              puts("REPEAT");
+            else
+              puts("WRONG");
+          }
+        
+          return 0;
         }
-        tag[u] = 1;
-      }
-    
-      scanf("%d", &m);
-    
-      while (m--) {
-        scanf("%s", s + 1);
-        int u = 1;
-        for (int j = 1; s[j]; ++j) {
-          int c = s[j] - 'a';
-          u = ch[u][c];
-          if (!u) break;  // 不存在对应字符的出边说明名字不存在
-        }
-        if (tag[u] == 1) {
-          tag[u] = 2;
-          puts("OK");
-        } else if (tag[u] == 2)
-          puts("REPEAT");
-        else
-          puts("WRONG");
-      }
-    
-      return 0;
-    }
-    ```
+        ```
 
 ### AC 自动机
 
@@ -106,82 +110,86 @@ Trie 是 [AC 自动机](./ac-automaton.md) 的一部分。
 
 将数的二进制表示看做一个字符串,就可以建出字符集为 $\{0,1\}$ 的 Trie 树。
 
-####  [BZOJ1954 最长异或路径](https://www.luogu.org/problem/P4551) 
+???+note "[BZOJ1954 最长异或路径](https://www.luogu.org/problem/P4551)"
+    给你一棵带边权的树,求 $(u, v)$ 使得 $u$ 到 $v$ 的路径上的边权异或和最大,输出这个最大值。
 
-随便指定一个根 $root$ ,用 $T(u, v)$ 表示 $u$ 和 $v$ 之间的路径的边权异或和,那么 $T(u,v)=T(root, u)\oplus T(root,v)$ ,因为 [LCA](../graph/lca.md) 以上的部分异或两次抵消了
+    点数不超过 $10^5$,边权在 $[0,2^{31})$ 内
 
-那么,如果将所有 $T(root, u)$ 插入到一棵 Trie 中,就可以对每个 $T(root, u)$ 快速求出和它异或和最大的 $T(root, v)$ :
+    ??? mdui-shadow-6 "题解"
+        随便指定一个根 $root$ ,用 $T(u, v)$ 表示 $u$ 和 $v$ 之间的路径的边权异或和,那么 $T(u,v)=T(root, u)\oplus T(root,v)$ ,因为 [LCA](../graph/lca.md) 以上的部分异或两次抵消了。
 
-从 Trie 的根开始,如果能向和 $T(root, u)$ 的当前位不同的子树走,就向那边走,否则没有选择。
+        那么,如果将所有 $T(root, u)$ 插入到一棵 Trie 中,就可以对每个 $T(root, u)$ 快速求出和它异或和最大的 $T(root, v)$ :
 
-贪心的正确性:如果这么走,这一位为 $1$ ;如果不这么走,这一位就会为 $0$ 。而高位是需要优先尽量大的
+        从 Trie 的根开始,如果能向和 $T(root, u)$ 的当前位不同的子树走,就向那边走,否则没有选择
 
-??? note "参考代码"
-    ```cpp
-    #include <algorithm>
-    #include <cstdio>
-    
-    const int N = 100010;
-    
-    int head[N], nxt[N << 1], to[N << 1], weight[N << 1], cnt;
-    int n, dis[N], ch[N << 5][2], tot = 1, ans;
-    
-    void insert(int x) {
-      for (int i = 30, u = 1; i >= 0; --i) {
-        int c = ((x >> i) & 1);
-        if (!ch[u][c]) ch[u][c] = ++tot;
-        u = ch[u][c];
-      }
-    }
-    
-    void get(int x) {
-      int res = 0;
-      for (int i = 30, u = 1; i >= 0; --i) {
-        int c = ((x >> i) & 1);
-        if (ch[u][c ^ 1]) {
-          u = ch[u][c ^ 1];
-          res |= (1 << i);
-        } else
-          u = ch[u][c];
-      }
-      ans = std::max(ans, res);
-    }
-    
-    void add(int u, int v, int w) {
-      nxt[++cnt] = head[u];
-      head[u] = cnt;
-      to[cnt] = v;
-      weight[cnt] = w;
-    }
-    
-    void dfs(int u, int fa) {
-      insert(dis[u]);
-      get(dis[u]);
-      for (int i = head[u]; i; i = nxt[i]) {
-        int v = to[i];
-        if (v == fa) continue;
-        dis[v] = dis[u] ^ weight[i];
-        dfs(v, u);
-      }
-    }
-    
-    int main() {
-      scanf("%d", &n);
-    
-      for (int i = 1; i < n; ++i) {
-        int u, v, w;
-        scanf("%d%d%d", &u, &v, &w);
-        add(u, v, w);
-        add(v, u, w);
-      }
-    
-      dfs(1, 0);
-    
-      printf("%d", ans);
-    
-      return 0;
-    }
-    ```
+        贪心的正确性:如果这么走,这一位为 $1$ ;如果不这么走,这一位就会为 $0$ 。而高位是需要优先尽量大的。
+
+    ??? mdui-shadow-6 "参考代码"
+        ```cpp
+        #include <algorithm>
+        #include <cstdio>
+        
+        const int N = 100010;
+        
+        int head[N], nxt[N << 1], to[N << 1], weight[N << 1], cnt;
+        int n, dis[N], ch[N << 5][2], tot = 1, ans;
+        
+        void insert(int x) {
+          for (int i = 30, u = 1; i >= 0; --i) {
+            int c = ((x >> i) & 1);
+            if (!ch[u][c]) ch[u][c] = ++tot;
+            u = ch[u][c];
+          }
+        }
+        
+        void get(int x) {
+          int res = 0;
+          for (int i = 30, u = 1; i >= 0; --i) {
+            int c = ((x >> i) & 1);
+            if (ch[u][c ^ 1]) {
+              u = ch[u][c ^ 1];
+              res |= (1 << i);
+            } else
+              u = ch[u][c];
+          }
+          ans = std::max(ans, res);
+        }
+        
+        void add(int u, int v, int w) {
+          nxt[++cnt] = head[u];
+          head[u] = cnt;
+          to[cnt] = v;
+          weight[cnt] = w;
+        }
+        
+        void dfs(int u, int fa) {
+          insert(dis[u]);
+          get(dis[u]);
+          for (int i = head[u]; i; i = nxt[i]) {
+            int v = to[i];
+            if (v == fa) continue;
+            dis[v] = dis[u] ^ weight[i];
+            dfs(v, u);
+          }
+        }
+        
+        int main() {
+          scanf("%d", &n);
+        
+          for (int i = 1; i < n; ++i) {
+            int u, v, w;
+            scanf("%d%d%d", &u, &v, &w);
+            add(u, v, w);
+            add(v, u, w);
+          }
+        
+          dfs(1, 0);
+        
+          printf("%d", ans);
+        
+          return 0;
+        }
+        ```
 
 ### 可持久化字典树