zoukankan      html  css  js  c++  java
  • [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;
    }
    
  • 相关阅读:
    Python 特殊语法:filter、map、reduce、lambda
    db2 常用命令(二)
    db2 常用命令(一)
    java 基础
    No.16 selenium学习之路之异常处理
    python之assert断言
    python之uinttest单元测试框架
    测试用例
    No.15 selenium for python JavaScript
    No.14 selenium for python table表单
  • 原文地址:https://www.cnblogs.com/mrclr/p/10946572.html
Copyright © 2011-2022 走看看