zoukankan      html  css  js  c++  java
  • [CTSC2012]熟悉的文章 后缀自动机

    题面:洛谷

    题解:

      观察到L是可二分的,因此我们二分L,然后就只需要想办法判断这个L是否可行即可。

      因为要尽量使L可行,因此我们需要求出对于给定L,这个串最多能匹配上多少字符。

      如果我们可以对每个位置i求出g[i]表示以这个位置为结尾,向前最多匹配多少位,就可以快速得知任意区间[l, r]是否可以被匹配上,因为一个串如果可以被匹配上,那么它的子串肯定也可以被匹配上。

      然后我们再做一次DP,设f[i]为DP到i位,最多能匹配上多少字符

      那么朴素做法就是枚举上一段的结尾,然后更新,不过注意到这个决策是单调的,因此可以用单调队列优化一下。

      因为有g[i]和mid的限制,所以我们可以选取的上一段结尾的区间是[i - g[i], i - mid].

      所以在用单调队列维护的时候,为了保证右端点合法,每次不能立即把当前的i加入队列,而是要在下次枚举到区间右端点已经包括了i的时候再把i加入队列。(类似与NOIP普及组跳房子)

      

      这样就可以O(n)的求解。

      不过首先还需要求出g[i]....

      那么g[i]怎么求呢?  

      我们先建立广义后缀自动机,然后在自动机上匹配大串,假设当前匹配到的节点是now,上一次的长度是rnt。

      那么我们只有沿着parent树向上走,才可以保证l[当前节点]是合法的。

      因此我们不断向上走,每走到一个节点,都更新rnt = l[now], 直到走到一个节点使得它有当前字符对应的边,这个时候我们把rnt更新为rnt+1,并更新g数组

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 #define R register int
      4 #define AC 1100100
      5 #define ac 2300000
      6 
      7 int n, m, tail, head, len;
      8 int g[AC], f[AC], q[AC];//g[i]表示以i为结尾,最长能匹配的长度
      9 char s[AC];//f[i]表示DP到i位,能被覆盖的最大长度
     10 
     11 inline int read()
     12 {
     13     int x = 0;char c = getchar();
     14     while(c > '9' || c < '0') c = getchar();
     15     while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
     16     return x;
     17 }
     18 
     19 inline void upmax(int &a, int b)
     20 {
     21     if(b > a) a = b;
     22 }
     23 
     24 struct sam_auto{
     25     int ch[ac][26], l[ac], fa[ac], last, cnt;
     26 
     27     void add(int c)
     28     {    
     29         int p = last, np = ++ cnt;
     30         last = cnt, l[np] = l[p] + 1;
     31         for( ; !ch[p][c]; p = fa[p]) ch[p][c] = np;
     32         if(!p) fa[np] = 1;
     33         else
     34         {
     35             int q = ch[p][c];//找到对应节点
     36             if(l[p] + 1 == l[q]) fa[np] = q;
     37             else
     38             {
     39                 int nq = ++ cnt;
     40                 l[nq] = l[p] + 1;
     41                 memcpy(ch[nq], ch[q], sizeof(ch[q]));
     42                 fa[nq] = fa[q];
     43                 fa[q] = fa[np] = nq;
     44                 for( ; ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
     45             }
     46         }
     47     }
     48     
     49     void build()
     50     {
     51         cnt = 1;
     52         for(R i = 1; i <= m; i ++)
     53         {
     54             last = 1, scanf("%s", s + 1), len = strlen(s + 1);
     55             for(R j = 1; j <= len; j ++) add(s[j] - '0');
     56         }        
     57     }
     58     
     59     void get_g()//求g数组
     60     {
     61         int now = 1, rnt = 0;
     62         /*for(R i = 1; i <= len; i ++)
     63         {//要先更新g再向下跳,因为根据parent树的性质,只有从一个点向上走才能保证
     64             int v = s[i] - '0';//这个点中出现的串在遇到的点中都出现了,如果先向下走了就无法保证了
     65             while(now != 1 && !ch[now][v]) now = fa[now];//一直向上跳到可以匹配为止
     66             if(ch[now][v]) g[i] = l[now] + 1, now = ch[now][v];
     67         }*/
     68         for(R i = 1; i <= len; i ++)
     69         {
     70             int v = s[i] - '0';//因为当前点是上一个点往下走走到的, 所以当前点的l[now]其实不一定合法。。。只能保证l[fa[now]]合法
     71             while(now != 1 && !ch[now][v]) now = fa[now], rnt = l[now];//因此如果这个点是有v这个节点的话,也不能直接取l[now],
     72             if(ch[now][v]) g[i] = ++rnt, now = ch[now][v];//而是要保留上次的匹配长度
     73             else g[i] = 0, rnt = 0;
     74             //printf("%d ", g[i]);
     75         }
     76         //printf("
    ");
     77     }
     78 }sam;
     79 
     80 void pre()
     81 {
     82     n = read(), m = read();
     83     sam.build();
     84 }
     85 
     86 bool check(int mid)//上一段结尾区间[i - g[i], i - mid]
     87 {
     88     int last = 0;
     89     head = tail = 0;
     90     for(R i = 1; i <= len; i ++)
     91     {    
     92         f[i] = f[i - 1];//因为如果强制取区间内的值,就相当于强制当这个点必须能产生贡献就产生贡献,但是实际上不产生贡献,直接取f[i -1]可能会更优
     93         if(i - g[i] > i - mid) continue;//如果没有合法区间就只能这样了
     94         while(last < i - mid)//每次加入不能超过右区间以保证合法
     95         {
     96             ++ last;
     97             while(head <= tail && f[q[tail]] - q[tail] < f[last] - last) -- tail;
     98             q[++ tail] = last;
     99         }
    100         while(head <= tail && q[head] < i - g[i]) ++ head;//把不属于合法区间的都去掉
    101         //printf("%d ", q[head]);//去掉非法区间的应该放在后面,因为后面还有加点操作,可能会加入非法节点
    102         upmax(f[i], f[q[head]] + i - q[head]);
    103     }
    104     //printf("
    ");
    105     return f[len] * 10 >= len * 9;
    106 }
    107 
    108 int half()//l显然满足可二分性
    109 {
    110     int l = 0, r = len, mid;
    111     while(l < r)
    112     {
    113         mid = (l + r + 1) >> 1;
    114         if(check(mid)) l = mid;
    115         else r = mid - 1;
    116     } 
    117     return l;
    118 }
    119 
    120 void work()
    121 {
    122     for(R i = 1; i <= n; i ++)
    123     {
    124         scanf("%s", s + 1), len = strlen(s + 1);
    125         sam.get_g(), printf("%d
    ", half());
    126         //for(R j = 1; j <= n; j ++) g[j] = f[j] = 0;
    127     }
    128 }
    129 
    130 int main()
    131 {
    132 //    freopen("in.in", "r", stdin);
    133     pre();
    134     work();
    135 //    fclose(stdin);
    136     return 0;
    137 }
    View Code

      

      

  • 相关阅读:
    C#学习三之几个关键字Static,ref&out,get&set,readonly
    C#学习二之浅析var类型和enum枚举类型
    C#学习一之HelloWorld
    泛型(二)
    泛型(一)
    JavaScript 使用
    JavaScript
    C# 自定义控件制作和使用实例(winform)
    ThinkPHP 参数绑定原理
    处理jQuery append加入的元素 绑定事件无效的方法
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10067923.html
Copyright © 2011-2022 走看看