zoukankan      html  css  js  c++  java
  • 【题解】 区间本质不同子串个数 SAM+LCT+线段树 luogu6292

    Legend

    同标题。

    Link ( extrm{to Luogu})

    Editorial

    考虑离线。询问右端点从左到右排序。

    我们把每一个子串最后一次出现的位置的左端点设置成 (+1)。这样查询区间和就是答案。

    显然,经过 SAM 中一个节点 (x) 时,会更新 (x) 沿 fail 树到根这条路径上的子串的最后一次出现的位置

    这个更新操作就很像 LCT 的 access 操作。我们不妨就用 LCT 维护子串的最后一次出现的位置

    对于最后一次出现的位置相同的结点,它们在 fail 树上是一条链。

    所以长度是连续的,可以线段树区间修改最后一次出现的位置的左端点的权值。

    现在唯一的问题就是,这样做的时间复杂度是什么?

    一个结论是 LCT 的 access 内层循环次数是 (O(n log n)) 的,故复杂度正确。

    时间复杂度 (O(q log n + nlog^2 n))

    Editorial

    #include <bits/stdc++.h>
    
    #define debug(...) fprintf(stderr ,__VA_ARGS__)
    #define __FILE(x)
    	freopen(#x".in" ,"r" ,stdin);
    	freopen(#x".out" ,"w" ,stdout)
    #define LL long long
    
    const int MX = 2e5 + 23;
    const LL MOD = 998244353;
    
    int read(){
    	char k = getchar(); int x = 0;
    	while(k < '0' || k > '9') k = getchar();
    	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
    	return x;
    }
    
    char str[MX]; int q;
    
    struct SEGMENTTREE{
    
    	struct node{
    		int l ,r;
    		LL sum ,add;
    		node *lch ,*rch;
    	}*root;
    	void pushup(node *x){x->sum = x->lch->sum + x->rch->sum;}
    	void doadd(node *x ,LL v){x->add += v ,x->sum += (x->r - x->l + 1) * v;}
    	void pushdown(node *x){
    		if(x->add){
    			doadd(x->lch ,x->add);
    			doadd(x->rch ,x->add);
    			x->add = 0;
    		}
    	}
    	node *build(int l ,int r){
    		node *x = new node;
    		x->l = l ,x->r = r;
    		x->sum = x->add = 0LL;
    		if(l == r) x->lch = x->rch = nullptr;
    		else{
    			int mid = (l + r) >> 1;
    			x->lch = build(l ,mid);
    			x->rch = build(mid + 1 ,r);
    			pushup(x);
    		}return x;
    	}
    	void buildtree(int l ,int r){root = build(l ,r);}
    	void add(node *x ,int l ,int r ,LL v){
    		if(l <= x->l && x->r <= r) return doadd(x ,v);
    		pushdown(x);
    		if(l <= x->lch->r) add(x->lch ,l ,r ,v);
    		if(r > x->lch->r) add(x->rch ,l ,r ,v);
    		return pushup(x);
    	}
    	void add(int l ,int r ,LL v){add(root ,l ,r ,v);}
    	LL sum(node *x ,int l ,int r){
    		if(x->r < l || x->l > r) return 0LL;
    		if(l <= x->l && x->r <= r) return x->sum;
    		pushdown(x);
    		return sum(x->lch ,l ,r) + sum(x->rch ,l ,r);
    	}
    	LL sum(int l ,int r){return sum(root ,l ,r);}
    }S;
    
    struct LCT{
    #define lch(x) ch[x][0]
    #define rch(x) ch[x][1]
    	LCT(){cov[0] = 0;}
    	int ch[MX][2] ,fa[MX] ,cov[MX] ,v[MX];
    	int get(int x){return x == rch(fa[x]);}
    	int Nroot(int x){return get(x) || x == lch(fa[x]);}
    	void docov(int x ,int V){cov[x] = v[x] = V;}
    	void pushdown(int x){
    		if(cov[x] == 0) return;
    		if(lch(x)) docov(lch(x) ,cov[x]);
    		if(rch(x)) docov(rch(x) ,cov[x]);
    		cov[x] = 0;
    	}
    	void rotate(int x){
    		int f = fa[x] ,gf = fa[f] ,which = get(x) ,W = ch[x][!which];
    		if(Nroot(f)) ch[gf][get(f)] = x;
    		ch[x][!which] = f ,ch[f][which] = W;
    		if(W) fa[W] = f;
    		fa[f] = x ,fa[x] = gf;
    	}
    	int stk[MX] ,dep;
    	void splay(int x){
    		int f = x; stk[++dep] = f;
    		while(Nroot(f)) stk[++dep] = f = fa[f];
    		while(dep) pushdown(stk[dep--]);
    		while(Nroot(x)){
    			if(Nroot(f)) rotate(get(x) == get(f) ? f : x);
    			rotate(x);
    		};
    	}
    	void access(int x ,int id);
    	void link(int x ,int f){fa[x] = f;}
    #undef lch
    #undef rch
    }lct;
    
    struct SAM{
    	int tot ,las;
    	SAM(){tot = las = 1;}
    	struct NODE{int ch[26] ,len ,link;}a[MX];
    	void extend(int c){
    		int p = las ,cur = las = ++tot;
    		a[cur].len = a[p].len + 1;
    		for( ; p && !a[p].ch[c] ; p = a[p].link) a[p].ch[c] = cur;
    		if(!p) return a[cur].link = 1 ,void();
    		int q = a[p].ch[c];
    		if(a[p].len + 1 == a[q].len) return a[cur].link = q ,void();
    		int cl = ++tot;
    		a[cl] = a[q];
    		a[cl].len = a[p].len + 1;
    		a[q].link = a[cur].link = cl;
    		for( ; p && a[p].ch[c] == q ; p = a[p].link) a[p].ch[c] = cl;
    	}
    	void build(){ for(int i = 2 ; i <= tot ; ++i) lct.link(i ,a[i].link);}
    }A;
    
    int cnt;
    void LCT::access(int x ,int id){
    	if(id == 6){
    		debug("SADFASDFADSF
    ");
    	}
    	int rt = x ,y = 0;
    	for( ; x ; x = fa[y = x]){
    		++cnt;
    		splay(x) ,ch[x][1] = y;
    		
    		if(v[x] == 0 || x == 1) continue;
    		int k = fa[x];
    		S.add(v[x] - A.a[x].len + 1 ,v[x] - A.a[k].len ,-1);
    		//debug("del [%d ,%d]
    " ,v[x] - A.a[x].len + 1 ,v[x] - A.a[A.a[k].link].len);
    	}
    	docov(y ,id);
    	// debug("add [%d, %d]
    " ,id - A.a[rt].len + 1 ,id);
    	S.add(id - A.a[rt].len + 1 ,id ,1);
    }
    
    struct QUERY{
    	int l ,r ,id;
    }Q[MX];
    
    bool cmp(QUERY A ,QUERY B){return A.r < B.r;}
    
    LL output[MX];
    int main(){
    	__FILE(区间本质不同子串个数);
    	scanf("%s" ,str + 1);
    	int n = strlen(str + 1);
    	for(int i = 1 ; i <=  n ; ++i) A.extend(str[i] - 'a');
    	A.build();
    	
    	S.buildtree(0 ,n);
    
    	q = read();
    	for(int i = 1 ,l ,r ; i <= q ; ++i){
    		l = read() ,r = read();
    		Q[i] = (QUERY){l ,r ,i};
    	}
    	std::sort(Q + 1 ,Q + 1 + q ,cmp);
    	int R = 1 ,x = 1;
    	for(int i = 1 ,l ,r ; i <= q ; ++i){
    		l = Q[i].l ,r = Q[i].r;
    		while(R <= r){
    			x = A.a[x].ch[str[R] - 'a'];
    			lct.access(x ,R);
    			++R;
    		}
    		output[Q[i].id] = S.sum(l ,r);
    		debug("Query %d %d
    " ,l ,r);
    	}
    	for(int i = 1 ; i <= q ; ++i) printf("%lld
    " ,output[i]);
    	debug("%d
    " ,cnt);
    	return 0;
    }
    
  • 相关阅读:
    DriveInfo 类 提供对有关驱动器的信息的访问
    遍历数组 例子
    怎么判断点击dataGridView1的是第几列
    无法加载协定为“ServiceReference1.LanguageService”的终结点配置部分,因为找到了该协定的多个终结点配置。请按名称指示首选的终结点配置部分。
    c#面试题及答案(一)
    SQL杂谈 ,有你想要的...
    TextView和Button的学习
    GitHub的学习和使用
    App的布局管理
    EditText制作简单的登录界面
  • 原文地址:https://www.cnblogs.com/imakf/p/14259980.html
Copyright © 2011-2022 走看看