zoukankan      html  css  js  c++  java
  • 九省联考2018 制胡窜

    制胡窜

    对于一个字符串 (S),我们定义 (|S|) 表示 (S) 的长度。

    接着,我们定义 (S_i) 表示 (S) 中第 (i) 个字符,(S_{L,R}) 表示由 (S) 中从左往右数,第 (L) 个字符到第 (R) 个字符依次连接形成的字符串。特别的,如果 (L > R) ,或者 (L < [1, |S|]), 或者 (R < [1, |S|]) 我们可以认为 (S_{L,R}) 为空串。

    给定一个长度为 (n) 的仅由数字构成的字符串 (S),现在有 (q) 次询问,第 (k) 次询问会给出 (S) 的一个字符串 (S_{l,r}) ,请你求出有多少对 ((i, j)),满足 (1 le i < j le n)(i + 1 lt j),且 (S_{l,r}) 出现在 (S_{1,i}) 中或 (S_{i+1, j−1}) 中或 (S_{j,n}) 中。

    对于所有测试数据,(1 le n le 10^5)(1 le q le 3 · 10^5)(1 le l le r le n)

    题解

    参照cz_xuyixuanTS_Hugh的题解。

    问题跟所有子串有关,考虑后缀自动机。

    问题转化

    题目中两个“或”已经说明了正面求很难做。正难则反,计算(i,j)分布使得三段都不包含所有出现的串的方案数。显然((i,i+1),(j-1,j))这两个空隙要切断所有的串。所以问题转化到了空隙上面,把方案数求出来用(inom{n-1}2)减去它就是答案了。

    下面的论述为了方便,以右端点代替空隙,即用(i)来代替((i-1,i))这个空隙,显然(i,jin[2,n],i<j)

    只询问一个串

    首先,一个询问的答案只和询问串的在主串中所有出现的位置有关。因此要找出询问串在后缀自动机上的位置。

    定位一个询问的串可以在后缀自动机parent树上倍增在(O(log n))的时间内完成。如果能预处理出right集合那么所有出现的位置就找到了。

    考虑用线段树合并来做这件事。现在我们有了一棵维护着所有询问串出现位置的右端点的线段树,考虑如何得到答案。

    考虑较靠前的断点切断了哪些字符串。我们需要求出的即是:

    [sum_{k=ql}^{qr}询问串第一次出现和第k次出现的交∗询问串最后一次出现和第(k+1)次出现的交 ]

    其中([ql,qr])(k)可能的取值范围。(ql)即第(k+1)个串与后面的串有交集时,(k)的可取最小值;类似的,(qr)即第(k)和前面的串有交集时,(k)的可取最大值。它们可以在线段树上二分得到。

    记询问串第(k)次出现的左端点和右端点分别为(L_k)(R_k)
    对于询问串第一次出现和第(k)次出现的交,它在大部分的情况下为(L_{k+1}-L_k),也即(R_{k+1}-R_k),只有在(k=qr)时,它有可能为(R_1−L_i+1)
    对于询问串最后一次出现和第(k+1)次出现的交,它在大部分情况下为(R_{k+1}−L_{last}+1)

    还需要考虑一些特殊情况,可能可以用(i,j)中的一个切断所有串。为了不重不漏,对(i)来讨论,分为两种情况。

    1. (i)不切断任意的字符串,那么它一定在询问串第一次出现之前,并且(j)要恰好切断所有字符串,也即(j)的取值范围是询问串所有出现位置的并。
      只有在(k=ql)时,有可能对应这种情况,此时(ql=0)
    2. (i)切断了所有的字符串,那么(j)只需要满足在较靠前的断点之后即可,因此在这种情况下可能的方案数是一个等差数列的各项之和。
      只有在(k=qr)时有可能出现这种情况,此时(qr=last)

    那么,我们可以对(k=ql),和(k=qr)时特殊处理。对于剩下的情况,也即(kin(ql,qr))时,我们需要求出

    [sum^{qr−1}_{k=ql+1}(R_{k+1}−R_k)∗(R_{k+1}−L_{last}+1) \ =sum^{qr−1}_{k=ql+1}(R_{k+1}−R_k)∗R_{k+1}−(L_{last}−1)(R_{qr}−R_{ql+1}) ]

    对于求和部分,是关于两个相邻位置的信息,我们可以在线段树上额外维护这个信息。那么一个串就做完了。

    询问多个串

    由于使用线段树合并要求在线的话,合并的时候必须新建节点,所以空间复杂度就错了。所以把询问离线挂在parent树上,询问的时候在parent按从叶子到根的拓扑序来做。这样线段树合并的时候直接用原来的节点就行了。合并时线段树节点按访问量满来计算,最坏复杂度(O(nlog n))

    然后这道题就做完了。时间复杂度(O(nlog n+qlog n))

    代码

    这题给的数字串……写的字母串调了好久。

    co int N=2e5;
    int n,m;
    char s[N];
    ll ans[300001];
    // Interval Tree
    struct node{int min,max;ll sum;};
    node operator+(co node&a,co node&b){
    	node c=(node){a.min,b.max,a.sum+b.sum};
    	if(!c.min) c.min=b.min;
    	if(!c.max) c.max=a.max;
    	if(a.max&&b.min) c.sum+=(ll)b.min*(b.min-a.max);
    	return c;
    }
    namespace T{
    	node t[N*17];
    	int tot,lc[N*17],rc[N*17];
    	void insert(int&x,int l,int r,int p){
    		if(!x) x=++tot;
    		if(l==r) return t[x]=(node){l,l,0},void();
    		int mid=l+r>>1;
    		if(p<=mid) insert(lc[x],l,mid,p);
    		else insert(rc[x],mid+1,r,p);
    		t[x]=t[lc[x]]+t[rc[x]];
    	}
    	int merge(int x,int y){
    		if(!x||!y) return x+y;
    		lc[x]=merge(lc[x],lc[y]),rc[x]=merge(rc[x],rc[y]);
    		t[x]=t[lc[x]]+t[rc[x]];
    		return x;
    	}
    	int lower(int x,int l,int r,int p){ // first >=
    		if(t[x].min>=p) return t[x].min;
    		int mid=l+r>>1;
    		if(t[lc[x]].max&&t[lc[x]].max>=p) return lower(lc[x],l,mid,p);
    		else return lower(rc[x],mid+1,r,p);
    	}
    	int upper(int x,int l,int r,int p){ // last <=
    		if(t[x].max<=p) return t[x].max;
    		int mid=l+r>>1;
    		if(t[rc[x]].min&&t[rc[x]].min<=p) return upper(rc[x],mid+1,r,p);
    		else return upper(lc[x],l,mid,p);
    	}
    	node query(int x,int l,int r,int ql,int qr){
    		if(ql>qr) return (node){0,0,0};
    		if(ql<=l&&r<=qr) return t[x];
    		int mid=l+r>>1;
    		if(qr<=mid) return query(lc[x],l,mid,ql,qr);
    		if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
    		return query(lc[x],l,mid,ql,qr)+query(rc[x],mid+1,r,ql,qr);
    	}
    	ll query(int x,int len){ // represent gap with right vertice
    		if(len<=0) return 0;
    		int r1=t[x].min,l1=r1-len+1; // len=r-l+1-1
    		int rn=t[x].max,ln=rn-len+1;
    		int ql=lower(x,1,n,ln);
    		if(ql!=r1) ql=upper(x,1,n,ql-1);
    		else ql=0;
    		int qr=upper(x,1,n,r1+len-1);
    		if(ql>qr) return 0;
    		ll ans=0;
    		if(ql==qr){
    			assert(0<ql&&ql<rn);
    			ans+=(ll) (min(r1,lower(x,1,n,ql+1)-len)-(ql-len+1)+1) * (lower(x,1,n,ql+1)-ln+1);
    		}
    		else{
    			node tmp=query(x,1,n,ql+1,qr);
    			ans+=tmp.sum-(ll) (ln-1) * (tmp.max-tmp.min);
    			if(ql==0) ans+=(ll) (l1-2) * (r1-ln+1);
    			else ans+=(ll) (min(r1,lower(x,1,n,ql+1)-len)-(ql-len+1)+1) * (lower(x,1,n,ql+1)-ln+1);
    			if(qr==rn) ans+=(ll) (n-r1+n-ln) * (r1-ln+1) / 2;
    			else ans+=(ll) (min(r1,lower(x,1,n,qr+1)-len)-(qr-len+1)+1) * (lower(x,1,n,qr+1)-ln+1);
    		}
    		return ans;
    	}
    }
    // Suffix Automaton
    namespace SAM{
    	int last=1,tot=1;
    	int ch[N][10],fa[N],len[N],pos[N]; // pos:out->in
    	int root[N]; // for Interval Tree
    	void extend(int c,int po){
    		int p=last,cur=last=++tot;
    		len[cur]=len[p]+1,pos[po]=cur;
    		T::insert(root[cur],1,n,po);
    		for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
    		if(!p) fa[cur]=1;
    		else{
    			int q=ch[p][c];
    			if(len[q]==len[p]+1) fa[cur]=q;
    			else{
    				int clone=++tot;
    				memcpy(ch[clone],ch[q],sizeof ch[q]);
    				fa[clone]=fa[q],len[clone]=len[p]+1;
    				fa[cur]=fa[q]=clone;
    				for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
    			}
    		}
    	}
    	int anc[N][19];
    	vector<int> e[N];
    	vector<pair<int,int> > q[N];
    	void init(){
    		for(int i=1;i<=n;++i) extend(s[i]-'0',i);
    		for(int i=1;i<=tot;++i) anc[i][0]=fa[i],e[fa[i]].push_back(i);
    		for(int k=1;k<=18;++k)
    			for(int i=1;i<=tot;++i) anc[i][k]=anc[anc[i][k-1]][k-1];
    	}
    	void storequery(int l,int r,int id){
    		int len=r-l+1,p=pos[r];
    		for(int i=18;i>=0;--i)
    			if(SAM::len[anc[p][i]]>=len) p=anc[p][i];
    		q[p].push_back(make_pair(len,id));
    	}
    	void work(int p){
    		for(int i=0;i<e[p].size();++i)
    			work(e[p][i]),root[p]=T::merge(root[p],root[e[p][i]]);
    		for(int i=0;i<q[p].size();++i)
    			ans[q[p][i].second]=(ll)(n-1)*(n-2)/2-T::query(root[p],q[p][i].first-1); // available len for cutting
    	}
    }
    
    int main(){
    	read(n),read(m),scanf("%s",s+1);
    	SAM::init();
    	for(int l,r,i=1;i<=m;++i){
    		read(l),read(r);
    		SAM::storequery(l,r,i);
    	}
    	SAM::work(1);
    	for(int i=1;i<=m;++i) printf("%lld
    ",ans[i]);
    	return 0;
    }
    

    强烈推荐cz_xuyixuan的代码,他竟然把码风维护到这种题上。当初我早就放弃封装了。

  • 相关阅读:
    eclipse 智能提示
    android 入门 004 (同一个方法,点击实现不同的效果)
    android 入门 003 (点击事件)
    android 入门 002 (拨打电话,发送短信)
    android 入门 001 (界面布局)
    Eclipse智能提示及快捷键
    转 Android学习笔记: 学习过程中碰到的一些问题及解决方法
    flash视频器播放器代码
    asp.net MVC webservice 报次错解决方法
    快递单号规则
  • 原文地址:https://www.cnblogs.com/autoint/p/10809248.html
Copyright © 2011-2022 走看看