Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/CF700E.html
题解
首先建个SAM。
一个结论:对于parent树上任意一个点x,以及它所代表的子树内任意一个点y,设节点y代表的最长串为S,设节点x代表的串为T1,T2,T3,...,设 F(S,T) 表示串T在S中的出现次数,则 F(S,T1) = F(S,T2) = F(S,T3) = ...
证明:假设串 Ta 和 Tb 在 S 中的出现次数不同,且 |Ta|+1=|Tb| 则必然存在一个位置,使得将 Tb 放在这里的时候,它的最左端点不和 S 匹配,其他位置都匹配,这样的话,Tb 的 Right 集合至少比 Ta 多这个位置,与 “Ta,Tb 都是节点 x 代表的串” 矛盾。故原命题得证。
第二个结论:一定存在一个最优解,使得 $\forall 1<i\leq k$, $S_{i-1}$ 是 $S_i$ 的 Border 。
这个很好证,如果不是 Border ,把 $S_i$ 两边多出来的割掉一定不亏。
于是我们可以开始规划算法了。
设 $dp[S]$ 表示每次保证前一个串在后一个串中出现至少 2 次,从空串转移到串 $S$ 的最多转移次数。
我们把状态用 parent 树上的节点表示,由于第一个结论,对于每一个节点,我们可以只把这个节点代表的最长串作为有效状态;转移的时候,只要看看父亲的串在当前节点的串中出现次数是否至少2次,如果不到,就直接继承父亲的结果,否则更新为当前结果; 判断出现多少次需要处理出 Right 集合,线段树合并即可。
代码
#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
using namespace std;
typedef long long LL;
LL read(){
LL x=0,f=0;
char ch=getchar();
while (!isdigit(ch))
f|=ch=='-',ch=getchar();
while (isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
const int N=200005*2;
int n;
namespace seg{
const int S=N*50;
int ls[S],rs[S],size[S],cnt,root;
void Init(){
cnt=root=0;
clr(ls),clr(rs),clr(size);
}
void Ins(int &rt,int L,int R,int x){
if (!rt)
rt=++cnt;
size[rt]++;
if (L==R)
return;
int mid=(L+R)>>1;
if (x<=mid)
Ins(ls[rt],L,mid,x);
else
Ins(rs[rt],mid+1,R,x);
}
int Merge(int a,int b,int L,int R){
if (!a||!b)
return a+b;
int rt=++cnt;
if (L==R)
size[rt]=1;
else {
int mid=(L+R)>>1;
ls[rt]=Merge(ls[a],ls[b],L,mid);
rs[rt]=Merge(rs[a],rs[b],mid+1,R);
size[rt]=size[ls[rt]]+size[rs[rt]];
}
return rt;
}
int Query(int rt,int L,int R,int xL,int xR){
if (!rt||xL>R||L>xR)
return 0;
if (xL<=L&&R<=xR)
return size[rt];
int mid=(L+R)>>1;
return Query(ls[rt],L,mid,xL,xR)
+Query(rs[rt],mid+1,R,xL,xR);
}
}
namespace SAM{
int last,size,root;
struct Node{
int Next[26],fa,Max,pos;
}t[N];
int Init(){
clr(t);
return last=size=root=1;
}
void extend(int c,int ps){
int p=last,np=++size,q,nq;
t[np].Max=t[p].Max+1,t[np].pos=ps;
for (;p&&!t[p].Next[c];p=t[p].fa)
t[p].Next[c]=np;
if (!p)
t[np].fa=root;
else {
q=t[p].Next[c];
if (t[p].Max+1==t[q].Max)
t[np].fa=q;
else {
nq=++size;
t[nq]=t[q],t[nq].Max=t[p].Max+1,t[nq].pos=ps;
t[np].fa=t[q].fa=nq;
for (;p&&t[p].Next[c]==q;p=t[p].fa)
t[p].Next[c]=nq;
}
}
last=np;
}
int id[N],tax[N],rt[N];
void sort(){
clr(tax);
for (int i=1;i<=size;i++)
tax[t[i].Max]++;
for (int i=1;i<=size;i++)
tax[i]+=tax[i-1];
for (int i=1;i<=size;i++)
id[tax[t[i].Max]--]=i;
}
void build(){
sort();
seg::Init();
for (int i=size;i>1;i--)
seg::Ins(rt[id[i]],1,n,t[id[i]].pos);
for (int i=size;i>1;i--){
int x=id[i],f=t[x].fa;
rt[f]=seg::Merge(rt[f],rt[x],1,n);
}
}
int dp[N],nid[N];
int Horse_NMDP(){
int ans=0;
dp[1]=0,nid[1]=1;
for (int i=2;i<=size;i++){
int x=id[i],f=nid[t[x].fa];
if (f==1||seg::Query(rt[f],1,n,t[x].pos-t[x].Max+t[f].Max
,t[x].pos)>=2)
dp[x]=dp[f]+1,nid[x]=x;
else
dp[x]=dp[f],nid[x]=f;
ans=max(ans,dp[x]);
}
return ans;
}
}
using SAM::t;
using SAM::extend;
char s[N];
int main(){
n=read();
scanf("%s",s+1);
SAM::Init();
for (int i=1;i<=n;i++)
extend(s[i]-'a',i);
SAM::build();
cout<<SAM::Horse_NMDP()<<endl;
return 0;
}
Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划的更多相关文章
- Codeforces.700E.Cool Slogans(后缀自动机 线段树合并 DP)
题目链接 \(Description\) 给定一个字符串\(s[1]\).一个字符串序列\(s[\ ]\)满足\(s[i]\)至少在\(s[i-1]\)中出现过两次(\(i\geq 2\)).求最大的 ...
- 【Codeforces 1037H】Security(SAM & 线段树合并)
Description 给出一个字符串 \(S\). 给出 \(Q\) 个操作,给出 \(L, R, T\),求字典序最小的 \(S_1\),使得 \(S^\prime\) 为\(S[L..R]\) ...
- Codeforces 1276F - Asterisk Substrings(SAM+线段树合并+虚树)
Codeforces 题面传送门 & 洛谷题面传送门 SAM hot tea %%%%%%% 首先我们显然可以将所有能够得到的字符串分成六类:\(\varnothing,\text{*},s, ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- UOJ#395. 【NOI2018】你的名字 字符串,SAM,线段树合并
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ395.html 题解 记得同步赛的时候这题我爆0了,最暴力的暴力都没调出来. 首先我们看看 68 分怎么做 ...
- CF700E-Cool Slogans【SAM,线段树合并,dp】
正题 题目链接:https://www.luogu.com.cn/problem/CF700E 题目大意 给出一个字符串\(S\),求一个最大的\(k\)使得存在\(k\)个字符串其中\(s_1\)是 ...
- loj#2059. 「TJOI / HEOI2016」字符串 sam+线段树合并+倍增
题意:给你一个子串,m次询问,每次给你abcd,问你子串sa-b的所有子串和子串sc-d的最长公共前缀是多长 题解:首先要求两个子串的最长公共前缀就是把反过来插入变成最长公共后缀,两个节点在paren ...
- CodeForces - 666E: Forensic Examination (广义SAM 线段树合并)
题意:给定字符串S,然后M个字符串T.Q次询问,每次给出(L,R,l,r),问S[l,r]在L到R这些T字符串中,在哪个串出现最多,以及次数. 思路:把所有串建立SAM,然后可以通过倍增走到[l,r] ...
- 2019.02.27 bzoj4556: [Tjoi2016&Heoi2016]字符串(二分答案+sam+线段树合并)
传送门 题意:给一个字符串SSS. 有mmm次询问,每次给四个参数a,b,c,da,b,c,da,b,c,d,问s[a...b]s[a...b]s[a...b]的所有子串和s[x...y]s[x... ...
随机推荐
- DataTable转list时 可空类型的转换问题
public class UtilHelper { public static IList<T> ConvertTo<T>(DataTable table) { if (tab ...
- np.array.all()和np.array.any()函数
np.array.all()是对np.array中所有元素进行与操作,然后结果返回True或False np.array.any()是对np.array中所有元素进行或操作,然后结果返回True或Fa ...
- vbox安装增强功能,实现宿主机文件夹共享并浏览器访问
虚拟机版本:6.0.4 r128413 (Qt5.6.2) linux:centos7/6 点击菜单栏中的设备->安装增强功能,再reboot 获取内核版本号 uname -r 查看yum的内核 ...
- php运行出现Call to undefined function curl_init()解决方法
php运行出现Call to undefined function curl_init() 64位win7/8 下PHP不支持CURL 除了将PHP.ini中的;extension=php_curl. ...
- Battery Historian 使用常用命令
一.重置电池数据收集数据 打开电池数据获取:adb shell dumpsys batterystats --enable full-wake-history 重置电池数据: adb shell du ...
- CMDB服务器管理系统【s5day88】:采集资产之Agent、SSH和Salt模式讲解
在对获取资产信息时,简述有四种方案. 1.Agent (基于shell命令实现) 原理图 Agent方式,可以将服务器上面的Agent程序作定时任务,定时将资产信息提交到指定API录入数据库 优点: ...
- .net Core 下数据库访问
SqlSugar :是一款高性能(达到ADO.NET最高性能水平)SqlSugar :是除EF外拉姆达解析最完善的ORM,多表 .UnionALL. 交叉子查询.真实的批量操作和分页SqlSugar ...
- Spark源码剖析 - SparkContext的初始化(一)
1. SparkContext概述 注意:SparkContext的初始化剖析是基于Spark2.1.0版本的 Spark Driver用于提交用户应用程序,实际可以看作Spark的客户端.了解Spa ...
- docker下安装mysql
docker run -d -p 3306:3306 -v /root/docker/mysql/conf/mysql.cnf:/etc/mysql/conf.d/mysql.cnf -v /root ...
- Node.js实战项目学习系列(2) 开发环境和调试工具
前言 上一节让我们对Node.js有一个初步的了解,那么现在可以开始正式学习下Node.js的开发了,但是任何一门语言要设计到开发,就必须先学习开发环境以及调试.本文将主要讲解这些内容. 本文涉及到的 ...