原文链接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,线段树合并,动态规划的更多相关文章

  1. Codeforces.700E.Cool Slogans(后缀自动机 线段树合并 DP)

    题目链接 \(Description\) 给定一个字符串\(s[1]\).一个字符串序列\(s[\ ]\)满足\(s[i]\)至少在\(s[i-1]\)中出现过两次(\(i\geq 2\)).求最大的 ...

  2. 【Codeforces 1037H】Security(SAM & 线段树合并)

    Description 给出一个字符串 \(S\). 给出 \(Q\) 个操作,给出 \(L, R, T\),求字典序最小的 \(S_1\),使得 \(S^\prime\) 为\(S[L..R]\) ...

  3. Codeforces 1276F - Asterisk Substrings(SAM+线段树合并+虚树)

    Codeforces 题面传送门 & 洛谷题面传送门 SAM hot tea %%%%%%% 首先我们显然可以将所有能够得到的字符串分成六类:\(\varnothing,\text{*},s, ...

  4. 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree

    原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...

  5. UOJ#395. 【NOI2018】你的名字 字符串,SAM,线段树合并

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ395.html 题解 记得同步赛的时候这题我爆0了,最暴力的暴力都没调出来. 首先我们看看 68 分怎么做 ...

  6. CF700E-Cool Slogans【SAM,线段树合并,dp】

    正题 题目链接:https://www.luogu.com.cn/problem/CF700E 题目大意 给出一个字符串\(S\),求一个最大的\(k\)使得存在\(k\)个字符串其中\(s_1\)是 ...

  7. loj#2059. 「TJOI / HEOI2016」字符串 sam+线段树合并+倍增

    题意:给你一个子串,m次询问,每次给你abcd,问你子串sa-b的所有子串和子串sc-d的最长公共前缀是多长 题解:首先要求两个子串的最长公共前缀就是把反过来插入变成最长公共后缀,两个节点在paren ...

  8. CodeForces - 666E: Forensic Examination (广义SAM 线段树合并)

    题意:给定字符串S,然后M个字符串T.Q次询问,每次给出(L,R,l,r),问S[l,r]在L到R这些T字符串中,在哪个串出现最多,以及次数. 思路:把所有串建立SAM,然后可以通过倍增走到[l,r] ...

  9. 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... ...

随机推荐

  1. [NOIp2016] 换教室

    题目类型:期望\(DP\) 传送门:>Here< 题意:现有\(N\)个时间段,每个时间段上一节课.如果不申请换教室,那么时间段\(i\)必须去教室\(c[i]\)上课,如果申请换课成功, ...

  2. 深入理解ES6箭头函数中的this

    简要介绍:箭头函数中的this,指向与一般function定义的函数不同,比较容易绕晕,箭头函数this的定义:箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定. 1.何为定义时 ...

  3. hdu 1848 简单SG函数

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1848 Problem Description 任何一个大学生对菲波那契数列(Fibonacci num ...

  4. 去掉 Chrome(V66) 新标签页的8个缩略图

    1.Chrome程序资源文件路径: C:\Program Files (x86)\Google\Chrome\Application\66.0.3359.181\resources.pak 2.下载C ...

  5. java String转int int转化为String

    String转int String str = "123"; int a = Integer.parseInt(str); System.out.println(a); Integ ...

  6. iOS开发者知识普及,Swift 挑战 Objective-C,谁会笑到最后?

    前言: 目前全球共有超过 7 亿台 iPhone 处于活跃状态,全球约有2000万名 iOS 开发者,这造就了 iOS 作为全球第二大移动设备平台的状态. 虽然安卓系统的全球市场占有率超过 iOS 系 ...

  7. DirectX11 With Windows SDK--13 动手实现一个简易Effects框架、阴影效果绘制

    前言 到现在为止,所有的教程项目都没有使用Effects11框架类来管理资源.因为在D3DCompile API (#47)版本中,如果你尝试编译fx_5_0的效果文件,会收到这样的警告: X4717 ...

  8. CSS iconfont阿里巴巴矢量图库在开发中实战使用

    前言 项目开发中,是避免不了使用小图标的,那么国内比较好用的图标网站当属iconfont了,下面我们将详细介绍如何使用. iconfont选择所需图标 1.iconfont官网 2.把所需要的添加进入 ...

  9. MyBatis使用注意事项

    目录 1. 使用何种映射器配置 2. 对象生命周期和作用域 SqlSessionFactoryBuilder SqlSessionFactory SqlSession 映射器实例(Mapper Ins ...

  10. python 写代码笔记 2017.6.15

    其实并不是越复杂的代码越好,简单高效才是好. 关键是思路和逻辑,还有多看别人写的代码. 学习到了:)