• @codeforces



    @description@

    给定一个包含 n 个小写字母的字符串 s,用 s 生成 n 个串 t1...n,其中 ti 等于字符串 s 将第 i 个字符替换为 * 得到的字符串。

    特别注意:这里的 * 只是一个字符,并不具有其他含义(如通配符)。

    求有多少字符串,在 {s, t1, t2, ..., tn} 中作为至少一个字符串的子串出现。

    戳我查看原题o.o

    @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
    ", ans);
    }
    

    @details@

    F 题好像比 E 题简单来着。。。

  • 相关阅读:
    mysql免安装版配置+navicat测试
    查询SQL Version详细信息
    拆分数据库测试之--收缩数据库
    测试拆分比较大SQL Server数据库
    SQL捕捉blocking信息
    T-SQL 重复读(Double Read)问题的理解
    Node.js版-七夕无事,人艰勿拆,求别说...
    css实现三角箭头(兼容IE6)
    前端开发的基础知识点摘要
    jQuery原理系列-常用Dom操作
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12093227.html
Copyright © 2020-2023  润新知