• [NOI2018]你的名字


    嘟嘟嘟


    这题以前写过弃掉了,后来竟然连自己的68分写法都看不懂了……
    这次回首这道题,心想怎么说也得把这题切了,哪怕抄题解也行。
    但没想到别人的题解自己怎么也看不懂,最终还是自己搞出来了(我真nb)。
    总用时前一天下午到第二天凌晨0:30+第二天半个上午。


    我们先来回顾(L = 1, R = n)的情况。
    大体思路就是求出本质不同的公共子串数目,然后用(T)串的本质不同的子串减去即可。
    (T)的本质不同的子串个数大家都会求,就不在这唠叨了。我们重点看前面的怎么求。
    我们在(T)的后缀自动机中插入一个字符(t[i])之后,马上记录(t[i])所在结点的父亲的长度,即(len[link[pos[i]]])。这个是什么东西呢?记前面那个值为(Len),他表示的是左端点为(1)~(Len - 1),右端点为(i)的这些子串第一次的出现位置都是(i)。因为新建的这个结点的endpos必定只有这个(i),那么属于这个结点的所有子串的出现位置也就只有(i)了。
    注意,一定是插入完后马上统计。如果把后缀自动机建完后再一个个统计就不对了。因为有的结点的父亲已经改变。
    我们记上面求出的这个数组为(ha[i])


    求出了上面这个东西后就好办了。我们把(T)放在(S)的后缀自动机上跑,记(T)到位置(i)时的匹配长度为(l),那么如果(l >ha[i])的话,就表示在在(T)的第(i)个位置,本质不同的公共子串数目增加了(l - ha[i]),把他加到答案里即可。

    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<cstdlib>
    #include<cctype>
    #include<vector>
    #include<stack>
    #include<queue>
    using namespace std;
    #define enter puts("") 
    #define space putchar(' ')
    #define Mem(a, x) memset(a, x, sizeof(a))
    #define In inline
    typedef long long ll;
    typedef double db;
    const int INF = 0x3f3f3f3f;
    const db eps = 1e-8;
    const int maxn = 5e5 + 5;
    inline ll read()
    {
      ll ans = 0;
      char ch = getchar(), last = ' ';
      while(!isdigit(ch)) last = ch, ch = getchar();
      while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
      if(last == '-') ans = -ans;
      return ans;
    }
    inline void write(ll x)
    {
      if(x < 0) x = -x, putchar('-');
      if(x >= 10) write(x / 10);
      putchar(x % 10 + '0');
    }
    
    char s[maxn], t[maxn];
    int ha[maxn];
    struct Sam
    {
      int cnt, las;
      int tra[maxn << 1][27], len[maxn << 1], link[maxn << 1];
      In void init()
      {
        link[cnt = las = 0] = -1;
        Mem(tra[0], 0);
      }
      In void insert(int c)
      {
        int now = ++cnt, p = las; Mem(tra[cnt], 0);
        len[now] = len[las] + 1;
        while(~p && !tra[p][c]) tra[p][c] = now, p = link[p];
        if(p == -1) link[now] = 0;
        else
          {
            int q = tra[p][c];
            if(len[q] == len[p] + 1) link[now] = q;
        	else
          	{
              int clo = ++cnt; Mem(tra[cnt], 0);
              memcpy(tra[clo], tra[q], sizeof(tra[q]));
              len[clo] = len[p] + 1;
              link[clo] = link[q], link[q] = link[now] = clo;
              while(~p && tra[p][c] == q) tra[p][c] = clo, p = link[p];
            }
          }
        las = now;
      }
      int pos[maxn << 1], buc[maxn << 1];
      In ll lcs(char* s)
      {
        ll ret = 0;
        int n = strlen(s);
        for(int i = 0, l = 0, p = 0; i < n; ++i)
          {
    	    int c = s[i] - 'a';
    	    while(~p && !tra[p][c]) p = link[p], l = len[p];
    	    if(p == -1) p = l = 0;
    	    else ++l, p = tra[p][c];
    	    if(l > ha[i]) ret += l - ha[i];
          }
        return ret;
      }
    }S, T;
    
    In void work0()
    {
      int len = strlen(t);
      ll tp = 0;
      for(int i = 0; i < len; ++i) tp += i + 1 - ha[i];    //根据ha[i]的定义,我们也可以这么求本质不同的子串个数
      write(tp - S.lcs(t)), enter;
    }
    
    int main()
    {
      scanf("%s", s);
      int n = strlen(s); S.init();
      for(int i = 0; i < n; ++i) S.insert(s[i] - 'a');
      int m = read();
      for(int i = 1; i <= m; ++i)
        {
          scanf("%s", t); int len = strlen(t);
          T.init();
          for(int j = 0; j < len; ++j) T.insert(t[j] - 'a'), ha[j] = T.len[T.link[T.las]];
          int L = read(), R = read();
          if(L == 1 && R == n) {work0(); continue;}
        }
      return 0;
    }
    

    当当当当!下面开始讲正解! 现在有了$L, R$的限制。一个直观的想法就是线段树合并求出每一个结点的endpos集合。然后匹配的时候除了满足这个节点有字符$c$的转移,还要保证转移后到达的结点的endpos有在$[L, R]$里面的。 这样一直跳,如果他最终在位置$i$时的匹配长度为$l$,我们要考虑从endpos向前$l$长度的字符串超没超出$L$的限制,即$endpos - l$是否小于$L$,如果小于,那么匹配的长度实际是$endpos - L + 1$。然后看这个长度是否大于$ha[i]$,如果大于,就减去$ha[i]$加到答案里。 那么为了让匹配的长度尽量不超过下界$L$,我们要找的是在$[L, R]$区间内尽量靠右的endpos。这个线段树维护最大值即可。
    表面上看这么做似乎就万事大吉了。但实际上68分以后照样WA。 为啥咧? 因为我们查找endpos的时候,是在能匹配的最长长度的结点上的线段树查询的,长度越长,endpos就越少,也就说明,我们在较长的匹配上查到的endpos减去$l$后会超过$L$的限制,而且可能超出很多,导致剩下的部分比$ha[i]$小。但是如果我们在匹配长度较短的线段树上查找的话,反而能找到一个endpos,在$L$的限制之内匹配的长度比$ha[i]$大,或者所有的匹配长度干脆都不会超出$L$。这时候就需要把这些长度加到答案里。 因此,匹配的时候,我们不仅要在这个结点的线段树上查找endpos计算贡献。还要到他的祖先结点上执行同样的过程,直到匹配长度不受$L$的限制再退出。接着去匹配下一位。
    自认为码风还是十分工整的。
    #include<cstdio>
    #include<iostream>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<cstdlib>
    #include<cctype>
    #include<vector>
    #include<stack>
    #include<queue>
    using namespace std;
    #define enter puts("") 
    #define space putchar(' ')
    #define Mem(a, x) memset(a, x, sizeof(a))
    #define In inline
    typedef long long ll;
    typedef double db;
    const int INF = 0x3f3f3f3f;
    const db eps = 1e-8;
    const int maxn = 5e5 + 5;
    const int maxt = 4e7 + 5;
    inline ll read()
    {
      ll ans = 0;
      char ch = getchar(), last = ' ';
      while(!isdigit(ch)) last = ch, ch = getchar();
      while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
      if(last == '-') ans = -ans;
      return ans;
    }
    inline void write(ll x)
    {
      if(x < 0) x = -x, putchar('-');
      if(x >= 10) write(x / 10);
      putchar(x % 10 + '0');
    }
    In void MYFILE()
    {
    #ifndef mrclr
      freopen("name.in", "r", stdin);
      freopen("name.out", "w", stdout);
    #endif
    }
    
    int n, L, R;
    char s[maxn], t[maxn];
    int ha[maxn];
    
    struct Tree
    {
      int ls, rs;
      int rpos;
    }seg[maxt];
    int root[maxn << 1], cur[maxn], tcnt = 0;
    #define LS seg[now].ls
    #define RS seg[now].rs
    In void update(int& now, int l, int r, int id)
    {
      now = ++tcnt;
      seg[now].rpos = -1;
      if(l == r) {seg[now].rpos = l; return;}
      int mid = (l + r) >> 1;
      if(id <= mid) update(LS, l, mid, id);
      else update(RS, mid + 1, r, id);
      seg[now].rpos = max(seg[LS].rpos, seg[RS].rpos);
    }
    In int merge(int x, int y, int l, int r)
    {
      if(!x || !y) return x | y;
      if(l == r) {seg[x].rpos |= seg[y].rpos; return x;}
      int mid = (l + r) >> 1, z = ++tcnt;
      seg[z].ls = merge(seg[x].ls, seg[y].ls, l, mid);
      seg[z].rs = merge(seg[x].rs, seg[y].rs, mid + 1, r);
      seg[z].rpos = max(seg[x].rpos, seg[y].rpos);
      return z;
    }
    In int query(int now, int l, int r, int L, int R)
    {
      if(!now) return -1;
      if(l == L && r == R) return seg[now].rpos;
      int mid = (l + r) >> 1;
      if(R <= mid) return query(LS, l, mid, L, R);
      else if(L > mid) return query(RS, mid + 1, r, L, R);
      else return max(query(LS, l, mid, L, mid), query(RS, mid + 1, r, mid + 1, R));
    }
    
    struct Sam
    {
      int cnt, las;
      int tra[maxn << 1][27], len[maxn << 1], link[maxn << 1];
      In void init()
      {
        link[cnt = las = 0] = -1;
        Mem(tra[0], 0);
      }
      In void insert(int c, int id)
      {
        int now = ++cnt, p = las; Mem(tra[cnt], 0);
        len[now] = len[las] + 1;
        while(~p && !tra[p][c]) tra[p][c] = now, p = link[p];
        if(p == -1) link[now] = 0;
        else
          {
    		int q = tra[p][c];
    		if(len[q] == len[p] + 1) link[now] = q;
    		else
    		  {
    		    int clo = ++cnt; Mem(tra[cnt], 0);
    		    memcpy(tra[clo], tra[q], sizeof(tra[q]));
    		    len[clo] = len[p] + 1;
    		    link[clo] = link[q], link[q] = link[now] = clo;
    		    while(~p && tra[p][c] == q) tra[p][c] = clo, p = link[p];
    		  }
          }
        las = now;
        if(~id) cur[id] = now;
      }
      int pos[maxn << 1], buc[maxn << 1];
      In void solve()
      {
        for(int i = 1; i <= cnt; ++i) ++buc[len[i]];
        for(int i = 1; i <= cnt; ++i) buc[i] += buc[i - 1];
        for(int i = 1; i <= cnt; ++i) pos[buc[len[i]]--] = i;
        for(int i = cnt; i; --i)
          {
    		int now = pos[i], fa = link[now];
    		root[fa] = merge(root[fa], root[now], 0, n - 1);
          }
      }
      In ll calc(char* s)
      {
        ll ret = 0;
        int m = strlen(s);
        for(int i = 0, l = 0, p = 0, pos = 0; i < m; ++i)
          {
    		int c = s[i] - 'a';
    		while(~p && (!tra[p][c] || (pos = query(root[tra[p][c]], 0, n - 1, L, R)) == -1))
    		  p = link[p], l = len[p];	//没有转移边或者[L, R]内没有endpos,就一直失配 
    		if(p == -1) p = l = 0;
    		else
    		  {
    		    ++l, p = tra[p][c];
    		    int tp = min(l, pos - L + 1);
    		    if(tp > ha[i]) ret += tp - ha[i];
    		    if(l > pos - L + 1)	//匹配长度仍受L的限制 
    		      {
    				int tp3 = max(tp, ha[i]), q = link[p], l2 = len[q];
    				while(~q)
    				  {
    				    int pos2 = query(root[q], 0, n - 1, L, R), tp2 = min(l2, pos2 - L + 1);
    				    if(tp2 > tp3) ret += tp2 - tp3;
    				    else if(l2 <= pos2 - L + 1) break;	//匹配长度不受L的限制了,退出 
    				    q = link[q], l2 = len[q], tp3 = max(tp2, ha[i]);
    				  }
    		      }
    		  }
          }
        return ret;
      }
    }S, T;
    
    int main()
    {
      MYFILE();
      scanf("%s", s);
      n = strlen(s); S.init();
      for(int i = 0; i < n; ++i)
        {
          S.insert(s[i] - 'a', i);
          update(root[cur[i]], 0, n - 1, i);
        }
      S.solve();
      int m = read();
      for(int i = 1; i <= m; ++i)
        {
          scanf("%s", t); int len = strlen(t);
          T.init();
          for(int j = 0; j < len; ++j) T.insert(t[j] - 'a', -1), ha[j] = T.len[T.link[T.las]];
          L = read() - 1, R = read() - 1;
          ll tp = 0;
          for(int j = 0; j < len; ++j) tp += j + 1 - ha[j];
          write(tp - S.calc(t)), enter;
        }
      return 0;
    }
    
  • 相关阅读:
    LeetCode 515. 在每个树行中找最大值(Find Largest Value in Each Tree Row)
    LeetCode 114. 二叉树展开为链表(Flatten Binary Tree to Linked List)
    LeetCode 199. 二叉树的右视图(Binary Tree Right Side View)
    LeetCode 1022. 从根到叶的二进制数之和(Sum of Root To Leaf Binary Numbers)
    LeetCode 897. 递增顺序查找树(Increasing Order Search Tree)
    LeetCode 617. 合并二叉树(Merge Two Binary Trees)
    LeetCode 206. 反转链表(Reverse Linked List) 16
    LeetCode 104. 二叉树的最大深度(Maximum Depth of Binary Tree)
    LeetCode 110. 平衡二叉树(Balanced Binary Tree) 15
    LeetCode 108. 将有序数组转换为二叉搜索树(Convert Sorted Array to Binary Search Tree) 14
  • 原文地址:https://www.cnblogs.com/mrclr/p/10946572.html
Copyright © 2020-2023  润新知