@codeforces - 1276F@ Asterisk Substrings
@description@
给定一个包含 n 个小写字母的字符串 s,用 s 生成 n 个串 t1...n,其中 ti 等于字符串 s 将第 i 个字符替换为 * 得到的字符串。
特别注意:这里的 * 只是一个字符,并不具有其他含义(如通配符)。
求有多少字符串,在 {s, t1, t2, ..., tn} 中作为至少一个字符串的子串出现。
@solution@
不包含 * 的子串即 s 的子串,经典问题。因此,我们只需要考虑 ti 中包含 * 的子串。
考虑 ti 中一个包含 * 的子串,总可以用 s[1...i-1] 的一个后缀 + * + s[i+1...n] 的一个前缀来表示。
因为 * 是固定的,所以又可以用一个二元组 (s[1...i-1]的某后缀, s[i+1...n]的某前缀) 表示一个含 * 的子串。
考虑建出正着建一遍后缀自动机 sam1,反着建一遍后缀自动机 sam2。
则 s[1...i-1] 在 sam1 中对应的结点到根的路径上的所有结点都可以与 s[i+1...n] 在 sam2 中对应的结点到根的路径上的所有结点结合成二元组。
接下来怎么统计?考虑 sam1 中的每个点,求出它的子树内所有结点对应到 sam2 上的链的并集,这个并集就是该点的贡献。
链并集有一个众所周知的做法:将点按照 dfs 序来排序,用所有点到根的链信息减去 dfs 序相邻两个点的 lca 到根的链信息。
因为要求子树内所有点的链并集,不难想到线段树合并。然后发现线段树合并的确可以维护(每次 pushup 时考虑左儿子的最右边的点与右儿子的最左边的点的 lca)。
注意一下空串是合法的。
时间复杂度 O(nlogn)(如果倍增求 lca 就是 O(nlog^2n))。
@accepted code@
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 200000;
#define rep(G, x) for(Graph::edge *p=G.adj[x];p;p=p->nxt)
struct Graph{
struct edge{
edge *nxt; int to;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt;
Graph() {ecnt = edges;}
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
}G1, G2;
struct SAM{
int fa[MAXN + 5], len[MAXN + 5], ch[26][MAXN + 5];
int root, ncnt, lst;
SAM() {root = ncnt = lst = 1; len[0] = -1;}
void copy(int nq, int q) {
for(int i=0;i<26;i++)
ch[i][nq] = ch[i][q];
fa[nq] = fa[q], len[nq] = len[q];
}
int extend(int x) {
int p = lst, nw = (++ncnt);
len[nw] = len[lst] + 1, lst = nw;
while( p && ch[x][p] == 0 )
ch[x][p] = nw, p = fa[p];
if( !p ) fa[nw] = root;
else {
int q = ch[x][p];
if( len[p] + 1 == len[q] )
fa[nw] = q;
else {
int nq = (++ncnt); copy(nq, q);
len[nq] = len[p] + 1, fa[q] = fa[nw] = nq;
while( p && ch[x][p] == q )
ch[x][p] = nq, p = fa[p];
}
}
return nw;
}
}S1, S2;
int cnt[MAXN + 5], fir[MAXN + 5], dfn[2*MAXN + 5], dep[MAXN + 5], dcnt;
void dfs1(int x, int f) {
dfn[++dcnt] = x, fir[x] = dcnt, dep[x] = dep[f] + 1;
rep(G2, x) {
if( p->to == f ) continue;
dfs1(p->to, x), dfn[++dcnt] = x;
}
cnt[x] = S2.len[x] + 1;
}
int lg[2*MAXN + 5], st[20][2*MAXN + 5];
void get_st() {
for(int i=1;i<=dcnt;i++) st[0][i] = dfn[i];
for(int i=2;i<=dcnt;i++) lg[i] = lg[i >> 1] + 1;
for(int j=1;j<20;j++) {
int t = (1 << (j - 1));
for(int i=1;i+t<=dcnt;i++)
st[j][i] = (dep[st[j-1][i]] < dep[st[j-1][i+t]] ? st[j-1][i] : st[j-1][i+t]);
}
}
int lca(int x, int y) {
int l = fir[x], r = fir[y];
if( l > r ) swap(l, r);
int k = lg[r - l + 1], p = (1 << k);
return (dep[st[k][l]] < dep[st[k][r-p+1]] ? st[k][l] : st[k][r-p+1]);
}
struct segtree{
struct node{
node *ch[2];
int lx, rx; ll res;
}pl[20*MAXN + 5], *NIL, *ncnt;
segtree() {
NIL = ncnt = pl;
NIL->ch[0] = NIL->ch[1] = NIL;
NIL->lx = NIL->rx = NIL->res = 0;
}
node *newnode() {
ncnt++;
ncnt->ch[0] = ncnt->ch[1] = NIL;
ncnt->lx = ncnt->rx = ncnt->res = 0;
return ncnt;
}
void pushup(node *x) {
x->lx = (x->ch[0] == NIL ? x->ch[1]->lx : x->ch[0]->lx);
x->rx = (x->ch[1] == NIL ? x->ch[0]->rx : x->ch[1]->rx);
x->res = x->ch[0]->res + x->ch[1]->res;
if( x->ch[0] != NIL && x->ch[1] != NIL )
x->res -= cnt[lca(dfn[x->ch[0]->rx], dfn[x->ch[1]->lx])];
}
void insert(node *&rt, int l, int r, int p) {
if( rt == NIL ) rt = newnode();
if( l == r ) {
rt->lx = rt->rx = p, rt->res = cnt[dfn[p]];
return ;
}
int m = (l + r) >> 1;
if( p <= m ) insert(rt->ch[0], l, m, p);
else insert(rt->ch[1], m + 1, r, p);
pushup(rt);
}
node *merge(node *rt1, node *rt2) {
if( rt1 == NIL ) return rt2;
if( rt2 == NIL ) return rt1;
rt1->ch[0] = merge(rt1->ch[0], rt2->ch[0]);
rt1->ch[1] = merge(rt1->ch[1], rt2->ch[1]);
pushup(rt1); return rt1;
}
}T;
segtree::node *rt[MAXN + 5];
ll ans;
void dfs2(int x, int f) {
rep(G1, x) {
if( p->to == f ) continue;
dfs2(p->to, x);
rt[x] = T.merge(rt[x], rt[p->to]);
}
ans += rt[x]->res * (S1.len[x] - S1.len[f]);
}
char s[MAXN + 5]; int n;
int pos1[MAXN + 5], pos2[MAXN + 5];
ll get_num() {
ll ret = 0;
for(int i=1;i<=S1.ncnt;i++)
ret += S1.len[i] - S1.len[S1.fa[i]];
return ret;
}
int main() {
scanf("%s", s + 1), n = strlen(s + 1);
for(int i=1;i<=n;i++) pos1[i] = S1.extend(s[i] - 'a');
for(int i=n;i>=1;i--) pos2[i] = S2.extend(s[i] - 'a');
pos1[0] = pos2[n+1] = 1;
for(int i=2;i<=S1.ncnt;i++) G1.addedge(S1.fa[i], i);
for(int i=2;i<=S2.ncnt;i++) G2.addedge(S2.fa[i], i);
ans = get_num();
dfs1(1, 0), get_st();
for(int i=0;i<=S1.ncnt;i++) rt[i] = T.NIL;
for(int i=1;i<=n;i++) T.insert(rt[pos1[i-1]], 1, dcnt, fir[pos2[i+1]]);
dfs2(1, 0);
printf("%lld\n", ans);
}
@details@
F 题好像比 E 题简单来着。。。
@codeforces - 1276F@ Asterisk Substrings的更多相关文章
- Codeforces 1276F - Asterisk Substrings(SAM+线段树合并+虚树)
Codeforces 题面传送门 & 洛谷题面传送门 SAM hot tea %%%%%%% 首先我们显然可以将所有能够得到的字符串分成六类:\(\varnothing,\text{*},s, ...
- codeforces #271D Good Substrings
原题链接:http://codeforces.com/problemset/problem/271/D 题目原文: D. Good Substrings time limit per test 2 s ...
- Codeforces 316G3 Good Substrings 字符串 SAM
原文链接http://www.cnblogs.com/zhouzhendong/p/9010851.html 题目传送门 - Codeforces 316G3 题意 给定一个母串$s$,问母串$s$有 ...
- CodeForces 550A Two Substrings(模拟)
[题目链接]click here~~ [题目大意]: You are given string s. Your task is to determine if the given string s ...
- Codeforces 271D - Good Substrings [字典树]
传送门 D. Good Substrings time limit per test 2 seconds memory limit per test 512 megabytes input stand ...
- Codeforces.392E.Deleting Substrings(区间DP)
题目链接 \(Description\) \(Solution\) 合法的子序列只有三种情况:递增,递减,前半部分递增然后一直递减(下去了就不会再上去了)(当然还要都满足\(|a_{i+1}-a_i| ...
- CodeForces 1110H. Modest Substrings
题目简述:给定$1 \leq l \leq r \leq 10^{800}$,求一个长度为$n \leq 2000$的数字串$s$,其含有最多的[好]子串.一个串$s$是[好]的,如果将其看做数字时无 ...
- Codeforces Round #606 (Div. 1) Solution
从这里开始 比赛目录 我菜爆了. Problem A As Simple as One and Two 我会 AC 自动机上 dp. one 和 two 删掉中间的字符,twone 删掉中间的 o. ...
- Codeforces Round #306 (Div. 2) A. Two Substrings 水题
A. Two Substrings Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/550/pro ...
随机推荐
- Spring注解驱动开发(一)-----组件注册
注册bean xml方式 1.beans.xml-----很简单,里面注册了一个person bean <?xml version="1.0" encoding=" ...
- 我是如何在实际项目中解决MySQL性能问题
可能是本性不愿随众的原因,我对于程序员面试中动辄就是考察并发上千万级别的QPS向来嗤之以鼻,好像国内的应用都是那么多用户量一样,其实并发达到千万,百万以上的应用能有几个? 绝大多数的程序员面临的只是解 ...
- 使用gRPC-Gateway快速构建微服务-双向认证下rpc-gateway使用(同时提供rpc和http接口)
https://github.com/grpc-ecosystem/grpc-gateway 在grpc之上加一层代理并转发,转变成protobuf格式来访问grpc服务 安装 go get -u g ...
- Lamdba表达式的代码使用讲解
public class Lambda{ public static void main(String[] args) { repeat(10, (i)->System.out.print(&q ...
- 使用 windows 批处理指令(BAT文件)进行压缩文件(zip)解压操作
以下指令包括文件删除.复制.zip文件解压操作.使用7z指令指令进行解压操作前,需要确保 windows 的 path 系统环境变量中存在7z的安装路径. 7z的下载地址:https://www.7- ...
- LuoguP3690 【模板】Link Cut Tree (动态树) LCT模板
P3690 [模板]Link Cut Tree (动态树) 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两 ...
- Luogu P3254 圆桌问题(最大流)
P3254 圆桌问题 题面 题目描述 假设有来自 \(m\) 个不同单位的代表参加一次国际会议.每个单位的代表数分别为 \(r_i (i =1,2,--,m)\) . 会议餐厅共有 \(n\) 张餐桌 ...
- 03Redis入门指南笔记(事务、生存时间、排序、消息通知、管道)
一:事务 1:概述 Redis中的事务(transaction)是一组命令的集合.事务同命令一样都是Redis的最小执行单位,一个事务中的命令要么都执行,要么都不执行. 事务的原理是是先将属于一个事务 ...
- android 数据绑定(1)Ativity、Fragment、Item绑定数据源
1.简介 官方文档: https://developer.android.com/topic/libraries/data-binding 官方示例: https://github.com/andr ...
- 直接在安装了redis的Linux机器上操作redis数据存储类型--List类型
一.概述: 在Redis中,List类型是按照插入顺序排序的字符串链表.和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素.在插入时,如果该键并不存在,Redis ...