[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("\n");
 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("\n");
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\n", 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

 

 

 

  

  

posted @ 2018-12-04 22:49  ww3113306  阅读(159)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。