●BZOJ 3998 [TJOI2015]弦论

题链:

http://www.lydsy.com/JudgeOnline/problem.php?id=3998
题解:

后缀自动机。
当T=0时,
由于在后缀自动机上沿着trans转移,每个串都是互不相同的,
就只需要统计出从每个状态出发,存在多少条不同的路径,即有多少个不同的子串。
这个可以拓扑排序后用DP解决。
转移: $$cnt[p]=\sum_{trans[p][*]=q,q!=0} cnt[q] + 1$$
然后配合cnt[]去dfs即可。
当T=1时,
这时考虑了相同的子串。但是不难发现,
如果沿着trans转移到了一个状态s,
那么s的Right集合大小就表示了这个串出现了多少次。
所以沿用上面T=0的做法,只是把DP转移稍稍修改一下:
转移: $$cnt[p]=\sum_{trans[p][*]=q,q!=0} cnt[q] + right[p]$$
(把+1改为+right[p即可],接下来同样是配合cnt[]去dfs。)
(本人代码跑得很慢,2333)

代码:

#include<bits/stdc++.h>
#define MAXN 500005
#define ll long long
using namespace std;
ll cnt[MAXN*3];
struct SAM{
	int size;
	int maxs[MAXN*3],trans[MAXN*3][26],parent[MAXN*3],right[MAXN*3];
	int Newnode(int a,int b){
		++size; maxs[size]=a;
		memcpy(trans[size],trans[b],sizeof(trans[b]));
		return size;
	}
	int Extend(int last,int x){
		static int p,np,q,nq;
		p=last; np=Newnode(maxs[p]+1,0);
		for(;p&&!trans[p][x];p=parent[p]) trans[p][x]=np;
		if(!p) parent[np]=1;
		else{
			q=trans[p][x];
			if(maxs[p]+1!=maxs[q]){
				nq=Newnode(maxs[p]+1,q);
				parent[nq]=parent[q];
				parent[q]=parent[np]=nq;
				for(;p&&trans[p][x]==q;p=parent[p]) trans[p][x]=nq;
			}
			else parent[np]=q;
		}
		return np;
	}
	void Build(char *S){
		static int p=1,last,tmp[MAXN],order[MAXN*3],len;
		memset(trans[0],0,sizeof(trans[0]));
		size=0; last=Newnode(0,0); len=strlen(S);
		for(int i=0;i<len;i++) last=Extend(last,S[i]-‘a‘);

		for(int i=0;i<len;i++) p=trans[p][S[i]-‘a‘],right[p]++;
		for(int i=1;i<=size;i++) tmp[maxs[i]]++;
		for(int i=1;i<=len;i++) tmp[i]+=tmp[i-1];
		for(int i=1;i<=size;i++) order[tmp[maxs[i]]--]=i;
		for(int i=size;i;i--) p=order[i],right[parent[p]]+=right[p];
	}
	void Count(int t){
		static queue<int> Q;
		static int in[MAXN*3],order[MAXN*3],ont;
		for(int p=1;p<=size;p++)
			for(int c=0;c<26;c++) if(trans[p][c])
				in[trans[p][c]]++;
		Q.push(1);
		while(!Q.empty()){
			int u=Q.front(); Q.pop(); order[++ont]=u;
			for(int c=0;c<26;c++) if(trans[u][c]){
				in[trans[u][c]]--;
				if(!in[trans[u][c]]) Q.push(trans[u][c]);
			}
		}
		for(int i=size,p;i;i--){
			p=order[i]; cnt[p]=p==1?0:t==0?1:right[p];
			for(int c=0;c<26;c++) if(trans[p][c])
				cnt[p]+=cnt[trans[p][c]];
		}
	}
}SUF;
void DFS(int p,int k,int t,int from){
	static int i;
	static char ans[MAXN];
	if(p==1) i=0;
	else{
		ans[i++]=from+‘a‘;
		k-=t==0?1:SUF.right[p];
	}
	if(k<=0) return (void)(ans[i]=0,puts(ans));
	for(int c=0;c<26;c++){
		if(k<=cnt[SUF.trans[p][c]]){
			DFS(SUF.trans[p][c],k,t,c);
			break;
		}
		k-=cnt[SUF.trans[p][c]];
	}
}
int main(){
	int T,K;
	static char S[MAXN];
	scanf("%s%d%d",S,&T,&K);
	SUF.Build(S);
	SUF.Count(T);
//	printf("%lld\n",cnt[1]);
	if(K<=cnt[1]) DFS(1,K,T,0);
	else puts("-1");
	return 0;
}

  

原文地址:https://www.cnblogs.com/zj75211/p/8541860.html

时间: 2024-11-13 14:37:10

●BZOJ 3998 [TJOI2015]弦论的相关文章

bzoj 3998: [TJOI2015]弦论(后缀自动机)

题目链接:bzoj 3998: [TJOI2015]弦论 题意: 对于一个给定长度为N的字符串,求它的第K小子串是什么. 题解: 后缀自动机O(n)*26解决. 对于op=0,num[i]=1,对于op=1,num[i]=cnt[i]. 因为cnt[i](即right集)表示以i节点结尾的后缀出现的次数. 1 #include<cstdio> 2 #include<cstring> 3 #define F(i,a,b) for(int i=a;i<=b;++i) 4 #def

[bzoj 3998][TJOI2015]弦论

传送门 Description 对于一个给定长度为N的字符串,求它的第\(K\)小子串是什么. \(T=0\)则表示不同位置的相同子串算作一个.\(T=1\)则表示不同位置的相同子串算作多个. Solution \(SAM\)可以用来维护子串的信息,而相类似的子串会由同一个状态来维护. 当\(T=0\)时,我们发现不需要维护\(Right\)集合的大小,我们不妨直接当它是\(1\) 为了查询答案,我们需要记录一下每个状态后继的大小和. Code? //2019.1.26 23:20~00:07

【BZOJ3998】[TJOI2015]弦论 后缀自动机

[BZOJ3998][TJOI2015]弦论 Description 对于一个给定长度为N的字符串,求它的第K小子串是什么. Input 第一行是一个仅由小写英文字母构成的字符串S 第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个.T=1则表示不同位置的相同子串算作多个.K的意义如题所述. Output 输出仅一行,为一个数字串,为第K小的子串.如果子串数目不足K个,则输出-1 Sample Input aabc 0 3 Sample Output aab HINT N<=5*10

bzoj 3997: [TJOI2015]组合数学

3997: [TJOI2015]组合数学 Description 给出一个网格图,其中某些格子有财宝,每次从左上角出发,只能向下或右走.问至少走多少次才能将财宝捡完.此对此问题变形,假设每个格子中有好多财宝,而每一次经过一个格子至多只能捡走一块财宝,至少走多少次才能把财宝全部捡完. Input 第一行为正整数T,代表数据组数. 每组数据第一行为正整数N,M代表网格图有N行M列,接下来N行每行M个非负整数,表示此格子中财宝数量,0代表没有 Output 输出一个整数,表示至少要走多少次. Samp

BZOJ 4000: [TJOI2015]棋盘( 状压dp + 矩阵快速幂 )

状压dp, 然后转移都是一样的, 矩阵乘法+快速幂就行啦. O(logN*2^(3m)) --------------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define b(x) (1 <&l

P3975 [TJOI2015]弦论

题目描述 为了提高智商,ZJY开始学习弦论.这一天,她在< String theory>中看到了这样一道问题:对于一个给定的长度为n的字符串,求出它的第k小子串是什么.你能帮帮她吗? 输入输出格式 输入格式: 第一行是一个仅由小写英文字母构成的字符串s 第二行为两个整数t和k,t为0则表示不同位置的相同子串算作一个,t为1则表示不同位置的相同子串算作多个.k的意义见题目描述. 输出格式: 输出数据仅有一行,该行有一个字符串,为第k小的子串.若子串数目不足k个,则输出-1. 输入输出样例 输入样

[TJOI2015]弦论 - 后缀自动机

下了狠心开始做SAM的题目了-- (中间因为傻逼26分写错被卡,进来的时候记得把自己的 cnt 减掉) // TJOI2015 XIAN LUN #include <bits/stdc++.h> using namespace std; const int Maxn = 2000005; struct Suffix_Automata { int maxlen[Maxn], trans[Maxn][26], link[Maxn], Size, Last; int t[Maxn], a[Maxn]

bzoj 3998

后缀自动机上dfs,查询第k大子串 注意代码复杂度,可以不需要加边,写简洁的代码 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 #define maxn 1000020 7 8 struct node{ 9 int val,pnt,size,degree; 10 int next[27]; 1

BZOJ 3998 后缀数组

思路: 第一问 建出来后缀数组以后  前缀和一发n-sa[i]-ht[i]+1  二分 第二问 二分判断是带重复的第几 怎么判断呢   找到它  往后扫ht递减sum+=它   跟K判判 注意等于 加一 之类的各种坑爹细节 要死.. //By SiriusRen #include <bits/stdc++.h> using namespace std; const int N=1000050; int n,cntA[N],cntB[N],A[N],B[N],rk[N],sa[N],tsa[N]