4212: 神牛的养成计划

4212: 神牛的养成计划

Time Limit: 10 Sec   Memory Limit: 256 MB
Submit: 86   Solved: 20
[ Submit][ Status][ Discuss]

Description

Hzwer成功培育出神牛细胞,可最终培育出的生物体却让他大失所望......
后来,他从某同校女神 牛处知道,原来他培育的细胞发生了基因突变,原先决定神牛特征的基因序列都被破坏了,神牛hzwer很生气,但他知道基因突变的低频性,说不定还有以下优秀基因没有突变,那么他就可以用限制性核酸内切酶把它们切出来,然后再构建基因表达载体什么的,后面你懂的......
黄学长现在知道了N个细胞的DNA序列,它们是若干个由小写字母组成的字符串。一个优秀的基因是两个字符串s1和s2,当且仅当s1是某序列的前缀的同时,s2是这个序列的后缀时,hzwer认为这个序列拥有这个优秀基因。
现在黄学长知道了M个优秀基因s1和s2,它们想知道对于给定的优秀基因,有多少个细胞的DNA序列拥有它。

Input

第一行:N,表示序列数
接下来N行,每行一个字符串,代表N个DNA序列,它们的总长为L1
接下来一个M,表示询问数
接下来M行,每行两个字符串s1和s2,由一个空格隔开,hzwer希望你能在线回答询问,所以s1等于“s1”的所有字符按字母表的顺序向后移动ans位(字母表是一个环),ans为上一个询问的答案,s2同理。例如ans=2  “s1”=qz
则s1=sb。对于第一个询问,ans=0
s1和s2的总长度为L2

Output

输出M行,每行一个数,第i行的数表示有多少个序列拥有第i个优秀基因。

Sample Input

10
emikuqihgokuhsywlmqemihhpgijkxdukjfmlqlwrpzgwrwozkmlixyxniutssasrriafu
emikuqihgokuookbqaaoyiorpfdetaeduogebnolonaoehthfaypbeiutssasrriafu
emikuqihgokuorocifwwymkcyqevdtglszfzgycbgnpomvlzppwrigowekufjwiiaxniutssasrriafu
emikuqihgokuorociysgfkzpgnotajcfjctjqgjeeiheqrepbpakmlixyxniutssasrriafu
emikuqihgokuorociysgfrhulymdxsqirjrfbngwszuyibuixyxniutssasrriafu
emikuqihgokuorguowwiozcgjetmyokqdrqxzigohiutssasrriafu
emikuqihgokuorociysgsczejjmlbwhandxqwknutzgdmxtiutssasrriafu
emikuqihgokuorociysgvzfcdxdiwdztolopdnboxfvqzfzxtpecxcbrklvtyxniutssasrriafu
emikuqihgokuorocsbtlyuosppxuzkjafbhsayenxsdmkmlixyxniutssasrriafu
emikuqihgokuorociysgfjvaikktsixmhaasbvnsvmkntgmoygfxypktjxjdkliixyxniutssasrriafu
10
emikuqihgokuorociysg yxniutssasrriafu
aiegqmedckgqknky eqpoowonnewbq
xfbdnjbazhdnhkhvb qrqgbnmlltlkkbtyn
bjfhrnfedlhrlolzfv qppxpoofxcr
zhdfpldcbjf stsidponnvnmmdvap
zhdfpldcbjfpjmjxdt gdstsidponnvnmmdvap
dlhjtphgfnjtnqnbhxr wxwmhtsrrzrqqhzet
bjfhrnfedlhrlolzfv frqppxpoofxcr
zhdfpldcbjf dponnvnmmdvap
ucyakgyxweakehes nondykjiiqihhyqvk

Sample Output

4
7
3
5
5
1
3
5
10
4

HINT

N<=2000

L1<=2000000

M<=100000

L2<=2000000

Source

[ Submit][ Status][ Discuss]

很直观的想法,,是暴力hash,O(N*M)检索,不过O(2E8)时限吃紧
假设所有的原串按照字典序从小到大排好一列
在这种情况下,每次询问只可能是这一列字符串的某一个区间中的某些串
可以用某些巧妙的方法先确定出这个区间
然后对于区间内的所有串,统计后缀恰为某一字符串的串的数量,就能得到答案了
∑L2 = 2E6,说明允许在和串长等复杂度时间内的算法
对于排序和确定区间,可以考虑使用trie,把所有的母串插入一棵trie,顺序自然就出来了
对于trie每个节点,维护其子树中的串的编号的左右极值,,也就是区间的左右端点
那么,确定前缀区间的操作就可以通过trie上的查询解决了
还剩下一个问题,,得到区间,后缀怎么统计??
将所有串翻转,按照排序后得到的顺序,建立一棵可持久化trie
那么,询问某一区间内含有某一后缀的母串的数量,在trie走一下确定端点,一个减法就出来了

这样做要构建两棵trie,,内存却只给256MB(丧心病狂......),按照普通trie每个点分配26个儿子铁定MLE了。。
不过,不管是什么样的trie,其中很多的点的儿子都是很稀疏的,就是给了26个儿子,实际存在的并没有这么多
那么,存边就改成使用邻接表,空间就变成线性,不过操作复杂度就由O(1)变成O(26)了
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

const int N = 2020;
const int maxn = 4E6 + 40;

struct E{
	int to,key; E(){}
	E(int to,int key): to(to),key(key){}
}edgs[maxn];

int n,m,Ans,root,cnt,Num,Cnt,last[maxn],from[maxn],siz[maxn]
	,L[maxn],R[maxn],rt[maxn],len[maxn],pos[maxn];
char st[maxn];

vector  s[N];
vector  v[maxn];
queue  Q;
stack  stk;

int Get(int x,int Nex)
{
	for (int i = last[x]; i; i = from[i])
	{
		if (edgs[i].key == Nex) return edgs[i].to;
		if (edgs[i].key < Nex) return 0;
	}
	return 0;
}

void Add(int x,int Nex,int Num)
{
	if (!last[x])
	{
		edgs[++Cnt] = E(Num,Nex);
		last[x] = Cnt; return;
	}
	if (edgs[last[x]].key == Nex) edgs[last[x]].to = Num;
	else if (edgs[last[x]].key < Nex)
		edgs[++Cnt] = E(Num,Nex),from[Cnt] = last[x],last[x] = Cnt;
	else
	{
		for (int i = last[x]; i; i = from[i])
			if (edgs[from[i]].key <= Nex)
			{
				if (edgs[from[i]].key == Nex) edgs[from[i]].to = Num;
				else edgs[++Cnt] = E(Num,Nex),from[Cnt] = from[i],from[i] = Cnt;
				return;
			}
	}
}

void Dfs(int x)
{
	for (int i = 0; i < v[x].size(); i++)
	{
		pos[++Num] = v[x][i];
		L[x] = min(L[x],Num);
		R[x] = max(R[x],Num);
	}
	stack  S; while (!S.empty()) S.pop();
	for (int i = last[x]; i; i = from[i]) S.push(edgs[i]);
	while (!S.empty())
	{
		int to = S.top().to; S.pop(); Dfs(to); 
		L[x] = min(L[x],L[to]); R[x] = max(R[x],R[to]);
	}
}

int Insert(int o,vector  &t,int le)
{
	int x,ret = ++cnt; siz[x = ret] = siz[o] + 1;
	for (int i = 0; i < le; i++)
	{
		int Nex = t[i] - 'a';
		for (int j = last[o]; j; j = from[j]) stk.push(edgs[j]);
		while (!stk.empty()) Add(x,stk.top().key,stk.top().to),stk.pop();
		Add(x,Nex,++cnt); x = Get(x,Nex);
		o = Get(o,Nex); siz[x] = siz[o] + 1;
	}
	for (int j = last[o]; j; j = from[j]) stk.push(edgs[j]);
	while (!stk.empty()) Add(x,stk.top().key,stk.top().to),stk.pop();
	return ret;
}

char nex(char s)
{
	int res = 'z' - s;
	if (Ans <= res) return Ans + s;
	else return 'a' - 1 + Ans - res;
}

int main()
{
	
	cin >> n; root = ++cnt; edgs[0].key = -1;
	for (int i = 1; i <= n; i++)
	{
		int x = root; scanf("%s",st); len[i] = strlen(st);
		for (int j = 0; j < len[i]; j++)
		{
			int Nex = st[j] - 'a';
			if (!Get(x,Nex)) Add(x,Nex,++cnt);
			x = Get(x,Nex); s[i].push_back(st[j]);
		}
		v[x].push_back(i); reverse(s[i].begin(),s[i].end());
	}
	
	for (int i = 1; i <= cnt; i++) L[i] = maxn; 
	Dfs(root); cin >> m; rt[0] = ++cnt;
	for (int i = 1; i <= n; i++) rt[i] = Insert(rt[i-1],s[pos[i]],len[i]);
	while (m--)
	{
		scanf("%s",st); int len = strlen(st),x = root;
		for (int i = 0; i < len; i++) x = Get(x,nex(st[i]) - 'a');
		scanf("%s",st); len = strlen(st); reverse(st,st + len);
		if (!x) {puts("0"); Ans = 0; continue;}
		int o1 = rt[L[x] - 1],o2 = rt[R[x]];
		for (int i = 0; i < len; i++)
		{
			int Nex = nex(st[i]) - 'a';
			o1 = Get(o1,Nex); o2 = Get(o2,Nex);
		}
		Ans = siz[o2] - siz[o1]; printf("%d\n",Ans); Ans %= 26;
	}
	return 0;
}

你可能感兴趣的:(trie)