[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数组
#include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 1100100
#define ac 2300000 int n, m, tail, head, len;
int g[AC], f[AC], q[AC];//g[i]表示以i为结尾,最长能匹配的长度
char s[AC];//f[i]表示DP到i位,能被覆盖的最大长度 inline int read()
{
int x = ;char c = getchar();
while(c > '' || c < '') c = getchar();
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x;
} inline void upmax(int &a, int b)
{
if(b > a) a = b;
} struct sam_auto{
int ch[ac][], l[ac], fa[ac], last, cnt; void add(int c)
{
int p = last, np = ++ cnt;
last = cnt, l[np] = l[p] + ;
for( ; !ch[p][c]; p = fa[p]) ch[p][c] = np;
if(!p) fa[np] = ;
else
{
int q = ch[p][c];//找到对应节点
if(l[p] + == l[q]) fa[np] = q;
else
{
int nq = ++ cnt;
l[nq] = l[p] + ;
memcpy(ch[nq], ch[q], sizeof(ch[q]));
fa[nq] = fa[q];
fa[q] = fa[np] = nq;
for( ; ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
}
}
} void build()
{
cnt = ;
for(R i = ; i <= m; i ++)
{
last = , scanf("%s", s + ), len = strlen(s + );
for(R j = ; j <= len; j ++) add(s[j] - '');
}
} void get_g()//求g数组
{
int now = , rnt = ;
/*for(R i = 1; i <= len; i ++)
{//要先更新g再向下跳,因为根据parent树的性质,只有从一个点向上走才能保证
int v = s[i] - '0';//这个点中出现的串在遇到的点中都出现了,如果先向下走了就无法保证了
while(now != 1 && !ch[now][v]) now = fa[now];//一直向上跳到可以匹配为止
if(ch[now][v]) g[i] = l[now] + 1, now = ch[now][v];
}*/
for(R i = ; i <= len; i ++)
{
int v = s[i] - '';//因为当前点是上一个点往下走走到的, 所以当前点的l[now]其实不一定合法。。。只能保证l[fa[now]]合法
while(now != && !ch[now][v]) now = fa[now], rnt = l[now];//因此如果这个点是有v这个节点的话,也不能直接取l[now],
if(ch[now][v]) g[i] = ++rnt, now = ch[now][v];//而是要保留上次的匹配长度
else g[i] = , rnt = ;
//printf("%d ", g[i]);
}
//printf("\n");
}
}sam; void pre()
{
n = read(), m = read();
sam.build();
} bool check(int mid)//上一段结尾区间[i - g[i], i - mid]
{
int last = ;
head = tail = ;
for(R i = ; i <= len; i ++)
{
f[i] = f[i - ];//因为如果强制取区间内的值,就相当于强制当这个点必须能产生贡献就产生贡献,但是实际上不产生贡献,直接取f[i -1]可能会更优
if(i - g[i] > i - mid) continue;//如果没有合法区间就只能这样了
while(last < i - mid)//每次加入不能超过右区间以保证合法
{
++ last;
while(head <= tail && f[q[tail]] - q[tail] < f[last] - last) -- tail;
q[++ tail] = last;
}
while(head <= tail && q[head] < i - g[i]) ++ head;//把不属于合法区间的都去掉
//printf("%d ", q[head]);//去掉非法区间的应该放在后面,因为后面还有加点操作,可能会加入非法节点
upmax(f[i], f[q[head]] + i - q[head]);
}
//printf("\n");
return f[len] * >= len * ;
} int half()//l显然满足可二分性
{
int l = , r = len, mid;
while(l < r)
{
mid = (l + r + ) >> ;
if(check(mid)) l = mid;
else r = mid - ;
}
return l;
} void work()
{
for(R i = ; i <= n; i ++)
{
scanf("%s", s + ), len = strlen(s + );
sam.get_g(), printf("%d\n", half());
//for(R j = 1; j <= n; j ++) g[j] = f[j] = 0;
}
} int main()
{
// freopen("in.in", "r", stdin);
pre();
work();
// fclose(stdin);
return ;
}
[CTSC2012]熟悉的文章 后缀自动机的更多相关文章
- [CTSC2012]熟悉的文章(后缀自动机+动态规划)
题目描述 阿米巴是小强的好朋友. 在小强眼中,阿米巴是一个作文成绩很高的文艺青年.为了获取考试作文的真谛,小强向阿米巴求教.阿米巴给小强展示了几篇作文,小强觉得这些文章怎么看怎么觉得熟悉,仿佛是某些范 ...
- P4022 [CTSC2012]熟悉的文章
题目 P4022 [CTSC2012]熟悉的文章 题目大意:多个文本串,多个匹配串,我们求\(L\),\(L\)指(匹配串中\(≥L\)长度的子串出现在文本串才为"熟悉",使得匹配 ...
- [CTSC2012]熟悉的文章(广义后缀自动机+二分答案+单调队列优化DP)
我们对作文库建出广义后缀自动机.考虑用\(SAM\)处理出来一个数组\(mx[i]\),表示从作文的第\(i\)个位置向左最远在作文库中出现的子串的长度.这个东西可以在\(SAM\)上跑\(trans ...
- 题解-CTSC2012 熟悉的文章
Problem bzoj 题目大意:给定多个标准串和一个文本串,全部为01串,如果一个串长度不少于\(L\)且是任意一个标准串的子串,那么它是"熟悉"的.对于文本串\(A\),把\ ...
- CTSC2012 熟悉的文章
传送门 首先很容易想到对于所有的模式串建出广义后缀自动机,之后对于我们每一个要检查的文本串,先在SAM上跑,计算出来每一个位置能匹配到的最远的位置是多少.(就是当前点减去匹配长度) 之后--考虑DP- ...
- 【[CTSC2012]熟悉的文章】
题目 好题啊 \(SAM\)+单调队列优化\(dp\) 首先这个\(L\)满足单调性真是非常显然我们可以直接二分 二分之后套一个\(dp\)就好了 设\(dp[i]\)表示到达\(i\)位置熟悉的文章 ...
- Luogu-4022 [CTSC2012]熟悉的文章
广义后缀自动机+DP 对于作文库建出广义后缀自动机,广义自动机就是在每次添加一个字符串之前把\(last=0\),然后正常添加就好了 对于每个询问串,预处理出每个位置\(i\)能向前匹配的最长长度\( ...
- [BZOJ2806][CTSC2012]熟悉的文章(Cheat)
bzoj luogu 题目描述 阿米巴是小强的好朋友. 在小强眼中,阿米巴是一个作文成绩很高的文艺青年.为了获取考试作文的真谛,小强向阿米巴求教.阿米巴给小强展示了几篇作文,小强觉得这些文章怎么看怎么 ...
- 【BZOJ2806】【CTSC2012】Cheat 广义后缀自动机+二分+Dp
题目 题目在这里 思路&做法 我们先对标准作文库建广义后缀自动机. 然后对于每一篇阿米巴的作文, 我们首先把放到广义后缀自动机跑一遍, 对于每一个位置, 记录公共子串的长度\((\)即代码和下 ...
随机推荐
- Shell if 判断语句参数
[ -a FILE ] 如果 FILE 存在则为真. [ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真. [ -c FILE ] 如果 FILE 存在且是一个字特殊文件则为真. [ ...
- Django之Models的class Meta
模型元数据是“任何不是字段的数据”,比如排序选项(ordering),数据库表名(db_table)或者人类可读的单复数名称(verbose_name 和verbose_name_plural).在模 ...
- python练习---小脚本
一.爬子域名 #!/usr/bin/python # -*- coding: utf-8 -*- import requests import re import sys def get(domain ...
- adb shell top 命令详解
[?25l[0m[H[J 当前系统时间 Tasks: 552 total, 1 running, 510 sleeping, 0 stopped, 0 zombie 任务(进程) 系统现在共有552个 ...
- 从零开始的Python学习Episode 11——装饰器
装饰器 装饰器是用来处理其他函数的函数,主要作用是在不修改原有函数的情况下添加新的功能,装饰器的返回值也是一个函数对象. 简单的装饰器 import time def show_time(f): de ...
- 小数第n位:高精度
小数第n位 问题描述 我们知道,整数做除法时,有时得到有限小数,有时得到无限循环小数. 如果我们把有限小数的末尾加上无限多个0,它们就有了统一的形式. 本题的任务是:在上面的约定下,求整数除法小数点后 ...
- Deeplearning - Overview of Convolution Neural Network
Finally pass all the Deeplearning.ai courses in March! I highly recommend it! If you already know th ...
- 安装VS的过程
软件工程学习到第三周,我们需要下载一个新的软件,用来进行软件测试.刚开始知道的时候觉得没甚么,不就是下个软件吗!有什么大不了的,分分钟搞定的事.可是想象很美好,现实很骨感.这是一个巨大的工作量呀,不仅 ...
- MathExamV2.0四则混合运算计算题生成器
MathExamV2.0四则混合运算计算题生成器----211606360 丁培晖 211606343 杨宇潇 一.预估与实际 PSP2.1 Personal Software Process Sta ...
- Java中的多态,引用类型的转换
1.多态分为引用多态和方法多态,见测试类 package com.wangcf; //父类 public class Animal { public void eat(){ System.out.pr ...