Codeforces 1276F - Asterisk Substrings(SAM+线段树合并+虚树)
SAM hot tea %%%%%%%
首先我们显然可以将所有能够得到的字符串分成六类:\(\varnothing,\text{*},s,\text{*}s,s\text{*},s\text{*}t\),其中 \(s,t\) 分别代表某个非空字符串,\(\text{*}\) 则代表题目中的星号,显然前两种情况的贡献都是 \(1\),算出后几种情况的答案后直接加 \(2\) 即可,第三种情况也异常简单,相当于求 \(s\) 中本质不同的子串个数,SAM 基操,相信来挑战这道题的人都会。第四种情况可以看作在某个 \(s[1…n-1]\) 的子串后面添上一个 \(\text{*}\),因此第四种情况合法的子串数就是 \(s[1…n-1]\) 本质不同子串个数,这个一波 SAM 带走即可,同理第五种情况合法的子串数就是 \(s[2…n]\) 本质不同子串数,同样一波 SAM 带走,比较棘手的是第六种情况。
由于我们要求本质不同子串个数,因此枚举星号代替了原字符串中的哪个字符是不明智的,因为这样会算重。因此我们考虑退一步,我们不是刚才对 \(s[1…n-1]\) 建了一个 SAM 吗,那么我们就考虑枚举星号左边那部分对应的子串在 SAM 上哪个节点对应的字符串集合中,假设为 \(x\),那么根据 SAM 那一套理论,这个字符串所有出现位置的右端点都在 \(x\) 对应的 \(\text{endpos}\) 中,也就是 \(x\) 在 parent tree 上子树内 \(s\) 所有前缀对应的节点。换句话说,所有星号可以替换的位置都在集合 \(\{y+1|y\in\text{endpos}(x)\}\) 中,考虑星号右边的字符串有哪些取值,那么显然,星号右边的字符串必须是这些星号可以放的位置右边的字符组成的后缀的某个前缀,也就是 \(S=\{s[y+2…n]|y\in\text{endpos}(x)\}\) 中某个字符串的某个前缀,也就是说我们只需求 \(S=\{s[y+2…n]|y\in\text{endpos}(x)\}\) 有多少个本质不同的前缀即可。直接枚举前缀显然不可取,不过注意到如果我们对反串也建一个 SAM,那么反串的 SAM 上某个节点 \(z\) 表示的字符串符合要求,当且仅当存在一个 \(y\in\text{endpos}(x)\),满足 \(s[y+2…n]\) 在反串的 SAM 上对应的节点在 \(z\) 的子树内,也就是说如果我们找出这样的 \(|\text{endpos}(x)|\) 个后缀在反串 SAM 上对应的节点,那么所有符合条件的点组成的集合就是它们到根节点路径上节点的并,而我们要求的,就是这个并中所有节点表示的字符串集合的大小之和。然后到这里我就不会做了,一直在想怎么剖。事实上这东西长得一脸虚树,根据虚树那一套理论,这东西节点并的字符串集合的大小,就是它们按照 DFS 序排序之后,DFS 序列相邻两个点+一头一尾路径上点的权值之和扣掉它们的 LCA 的权值的和除以 \(2\)(在本题中就是它们对应的等价类的大小,即 \(len_x-len_{lnk_x}\)),外加它们的 LCA 到根节点这段路径上权值之和。然后我们考虑线段树合并维护这货,具体来说就开一棵线段树,以 DFS 序为下标,如果一个叶节点存在则表示其表示的区间的 DFS 序在该节点的子树内存在,然后对于线段树上每个节点,维护该区间中,在 SAM 上存在的 DFS 序的最小值、最大值,以及两两之间的距离和,每次转移时将一个点为根的线段树与其儿子节点表示的线段树合并一波即可算出答案。初始状态大概就对于所有 \(i\in[1,n-2]\),我们找出长度为 \(i\) 的前缀在原串 SAM 上表示的节点,设其为 \(x\),然后假设 \(s[i+2…n]\) 在反串上表示的节点为 \(y\),那么我们就将 \(y\) 在反串上的 DFS 序加入 \(x\) 表示的线段树即可。使用 ST 表求 LCA 可以实现 \(\mathcal O(n\log n)\) 的复杂度。
const int MAXN=1e5;
const int MAXP=2e5;
const int MAX_ND=MAXP<<5;
const int LOG_N=19;
char str[MAXN+5];int n;
struct graph{
int hd[MAXP+5],to[MAXP+5],nxt[MAXP+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
};
struct SAM{
struct node{int ch[27],len,lnk;} s[MAXP+5];
int cur,ncnt,ed[MAXN+5];graph t;
SAM(){cur=ncnt=1;}
void extend(char c,int ps){
int id=c-'a',nw=++ncnt,p=cur;
s[nw].len=s[cur].len+1;ed[ps]=nw;cur=nw;
while(p&&!s[p].ch[id]) s[p].ch[id]=nw,p=s[p].lnk;
if(!p) return s[nw].lnk=1,void();
int q=s[p].ch[id];
if(s[q].len==s[p].len+1) return s[nw].lnk=q,void();
int cl=++ncnt;s[cl].len=s[p].len+1;
s[cl].lnk=s[q].lnk;s[q].lnk=cl;s[nw].lnk=cl;
for(int i=0;i<26;i++) s[cl].ch[i]=s[q].ch[i];
while(p&&s[p].ch[id]==q) s[p].ch[id]=cl,p=s[p].lnk;
}
void build(){for(int i=2;i<=ncnt;i++) t.adde(s[i].lnk,i);}
ll calc(){
ll res=0;
for(int i=1;i<=ncnt;i++) res+=s[i].len-s[s[i].lnk].len;
return res;
}
} s_tot,fw,bk;//forward backward
#define g1 fw.t
#define g2 bk.t
int dep[MAXP+5];pii st[MAXP*2+5][LOG_N+2];
int dfn[MAXP+5],rid[MAXP+5],tim=0,eul[MAXP+5],tim_eul=0;
void dfs0(int x,int f){
rid[dfn[x]=++tim]=x;
st[eul[x]=++tim_eul][0]=mp(dep[x],x);
for(int e=g2.hd[x];e;e=g2.nxt[e]){
int y=g2.to[e];if(y==f) continue;
dep[y]=dep[x]+1;dfs0(y,x);
st[eul[x]=++tim_eul][0]=mp(dep[x],x);
}
}
int getlca(int x,int y){
x=eul[x];y=eul[y];if(x>y) swap(x,y);
int k=31-__builtin_clz(y-x+1);
return min(st[x][k],st[y-(1<<k)+1][k]).se;
}
int getdis(int x,int y){
int lc=getlca(x,y);
return bk.s[x].len+bk.s[y].len-(bk.s[lc].len<<1);
}
struct data{
int lc,lft,rit;ll sum;
data(int _lc=0,int _lft=0,int _rit=0,ll _sum=0):
lc(_lc),lft(_lft),rit(_rit),sum(_sum){}
data operator +(const data &rhs){
if(!lc) return rhs;
if(!rhs.lc) return data(lc,lft,rit,sum);
return data(getlca(lc,rhs.lc),lft,rhs.rit,
sum+rhs.sum+getdis(rid[rit],rid[rhs.lft]));
}
};
namespace segtree{
struct node{int ch[2];data d;} s[MAX_ND+5];
int ncnt=0;
void pushup(int k){s[k].d=s[s[k].ch[0]].d+s[s[k].ch[1]].d;}
void insert(int &k,int l,int r,int x){
if(!k) k=++ncnt;if(l==r) return s[k].d=data(rid[x],x,x,0),void();
int mid=l+r>>1;
(x<=mid)?insert(s[k].ch[0],l,mid,x):insert(s[k].ch[1],mid+1,r,x);
pushup(k);
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;int z=++ncnt;
if(l==r) return s[z].d=data(rid[l],l,l,0),z;
int mid=l+r>>1;
s[z].ch[0]=merge(s[x].ch[0],s[y].ch[0],l,mid);
s[z].ch[1]=merge(s[x].ch[1],s[y].ch[1],mid+1,r);
return pushup(z),z;
}
}
using segtree::insert;
using segtree::merge;
int rt[MAXP+5];ll res=0;
void dfs(int x,int f){
for(int e=g1.hd[x];e;e=g1.nxt[e]){
int y=g1.to[e];if(y==f) continue;
dfs(y,x);rt[x]=merge(rt[x],rt[y],1,bk.ncnt);
} data d=segtree::s[rt[x]].d;
if(d.lc) res+=1ll*(fw.s[x].len-fw.s[fw.s[x].lnk].len)*
(d.sum+getdis(rid[d.lft],rid[d.rit])+(getdis(d.lc,1)<<1));
}
int main(){
scanf("%s",str+1);n=strlen(str+1);
for(int i=1;i<=n;i++) s_tot.extend(str[i],/*19260817*/i);
for(int i=1;i<n;i++) fw.extend(str[i],i);
for(int i=n;i>1;i--) bk.extend(str[i],i);
fw.build();bk.build();
dfs0(1,0);
for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=tim_eul;j++)
st[j][i]=min(st[j][i-1],st[j+(1<<i-1)][i-1]);
for(int i=1;i<n-1;i++) insert(rt[fw.ed[i]],1,bk.ncnt,dfn[bk.ed[i+2]]);
dfs(1,0);res>>=1;res+=2+s_tot.calc()+fw.calc()+bk.calc();
printf("%lld\n",res);
return 0;
}
Codeforces 1276F - Asterisk Substrings(SAM+线段树合并+虚树)的更多相关文章
- @codeforces - 1276F@ Asterisk Substrings
目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个包含 n 个小写字母的字符串 s,用 s 生成 n 个串 ...
- BZOJ5419[Noi2018]情报中心——线段树合并+虚树+树形DP
题目链接: [NOI2018]情报中心 题目大意:给出一棵n个节点的树,边有非负边权,并给出m条链,对于每条链有一个代价,要求选出两条有公共边的链使两条链的并的边权和-两条链的代价和最大. 花了一天的 ...
- UOJ#397. 【NOI2018】情报中心 线段树合并 虚树
原文链接www.cnblogs.com/zhouzhendong/p/UOJ397.com 前言 这真可做吗?只能贺题解啊-- 题解 我们称一条路径的 LCA 为这条路径两端点的 LCA. 我们将相交 ...
- luogu P4775 [NOI2018]情报中心 线段树合并 虚树 树的直径trick
LINK:情报中心 神题! 写了一下午 写到肚子疼. 调了一晚上 调到ex 用的是网上dalao的方法 跑的挺快的. 对于链的暴力 我不太会kk. 直接说正解吧: 分类讨论两种情况: 1 答案的两条链 ...
- 2016湖南省赛 I Tree Intersection(线段树合并,树链剖分)
2016湖南省赛 I Tree Intersection(线段树合并,树链剖分) 传送门:https://ac.nowcoder.com/acm/contest/1112/I 题意: 给你一个n个结点 ...
- 【BZOJ5329】【SDOI2018】战略游戏(圆方树,虚树)
[BZOJ5329][SDOI2018]战略游戏(圆方树,虚树) 题面 BZOJ 洛谷 Description 省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏. 这款战 ...
- bzoj5315/luoguP4517 [SDOI2018]战略游戏(圆方树,虚树)
bzoj5315/luoguP4517 [SDOI2018]战略游戏(圆方树,虚树) bzoj Luogu 题目描述略(太长了) 题解时间 切掉一个点,连通性变化. 上圆方树. $ \sum |S| ...
- CodeForces - 1037H: Security(SAM+线段树合并)
题意:给定字符串S: Q次询问,每次询问给出(L,R,T),让你在S[L,R]里面找一个字典序最小的子串,其字典序比T大. 没有则输出-1: 思路:比T字典序大,而且要求字典最小,显然就是在T的尾巴 ...
- CF666E Forensic Examination SAM+线段树合并+前缀树倍增
$ \color{#0066ff}{ 题目描述 }$ 给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l. ...
随机推荐
- vue3.x异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块 vue2.x 曾经简单的异步组件 components: { AsyncComponent: () =& ...
- [hi3521] nand flash 的 boot 启动模式的区别?
spi nand flash 的 boot 启动模式选择.0:1 线 boot:1:4 线 boot.请问,1线boot和4线boot有什么区别呢?该如何选择呢? 收藏 顶 踩 回复 使用 ...
- js计算精确度丢失问题解决
(function () { var calc = { /* 函数,加法函数,用来得到精确的加法结果 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显.这个函数返回较为精 ...
- 算法:数字推盘游戏--重排九宫(8-puzzle)
一.数字推盘游戏 数字推盘游戏(n-puzzle)是一种最早的滑块类游戏,常见的类型有十五数字推盘游戏和八数字推盘游戏等.也有以图画代替数字的推盘游戏.可能Noyes Palmer Chapman在1 ...
- Unity——射线系统
Unity射线系统 Demo展示 UI+Physical射线测试: FPS自定义射线测试: UGUI射线工具 实现功能,鼠标点击UI,返回鼠标点击的UI对象: 需要使用到鼠标点击事件-PointerE ...
- vector之erase和迭代器
C++开发中使用vector时非常方便的,但是也是需要非常小心的,最近在使用容器删除某个元素的时候,测试结果出现了异常 /* vector erase test*/ int testVector() ...
- 加法运算替代 牛客网 程序员面试金典 C++ Python
加法运算替代 牛客网 程序员面试金典 题目描述 请编写一个方法,实现整数的乘法.减法和除法运算(这里的除指整除).只允许使用加号. 给定两个正整数int a,int b,同时给定一个int type代 ...
- WPF进阶技巧和实战03-控件(3-文本控件及列表控件)
系列文章链接 WPF进阶技巧和实战01-小技巧 WPF进阶技巧和实战02-布局 WPF进阶技巧和实战03-控件(1-控件及内容控件) WPF进阶技巧和实战03-控件(2-特殊容器) WPF进阶技巧和实 ...
- Docker 制作Nginx镜像
参考文章:https://www.jianshu.com/p/dc4cd0547d1e 镜像的制作方式有两种,一种是下载别人的镜像之后再制作成自己的镜像,一种是从头开始制作自己的镜像 第一种,下载别人 ...
- anaconda无法launch应用(无法l打开任何应用)的问题解决 (点击应用无反应)
遇到了anaconda 无法launch 任何应用. 重装也不行. 先说我最终的解决方法(在官方文档中找到): 1. 启动 anaconda prompt , 输入 conda remove anac ...