Trie树/可持久化线段树
神题啊……搞了我一下午= =(其实第233个提交也是我的)
我一开始的思路:这个找kpm串的过程,其实就跟在AC自动机上沿fail倒着往下走是差不多的(看当前是哪些点的后缀,如果某个串的后缀是当前串,那它的fail就会指向这里)所以就在fail树上bfs一遍,然后找到所有的编号,排序后输出第k小……
然后顺利地WA了 QAQ
只好取膜拜题解……哦原来只要把每个串倒过来,建Trie树的时候,就相当于找当前这个串的子树!
在子树中统计啊……so easy啦~dfs一遍,把儿子中的编号传给父亲啦~然后就会发现光荣TLE了>_>
shen me gui!为什么呢?很简单啦,结点那么多,最坏情况下要把n个编号上传n次(比如有10W个相同的串,每个串都为 "aaa....aa"(10W个a))妥妥的TLE了。
等等……刚刚好像说是子树查询第K大?dfs序不是很好吗!瞬间转成求区间第K大!可持久化线段树!
ok,终于顺利AC……
1 /************************************************************** 2 Problem: 3439 3 User: Tunix 4 Language: C++ 5 Result: Accepted 6 Time:1240 ms 7 Memory:51688 kb 8 ****************************************************************/ 9 10 //BZOJ 3439 11 #include<vector> 12 #include<cstdio> 13 #include<cstring> 14 #include<cstdlib> 15 #include<iostream> 16 #include<algorithm> 17 #define rep(i,n) for(int i=0;i<n;++i) 18 #define F(i,j,n) for(int i=j;i<=n;++i) 19 #define D(i,j,n) for(int i=j;i>=n;--i) 20 #define pb push_back 21 using namespace std; 22 inline int getint(){ 23 int v=0,sign=1; char ch=getchar(); 24 while(ch<'0'||ch>'9'){ if (ch=='-') sign=-1; ch=getchar();} 25 while(ch>='0'&&ch<='9'){ v=v*10+ch-'0'; ch=getchar();} 26 return v*sign; 27 } 28 const int N=1e5+10,INF=~0u>>2; 29 typedef long long LL; 30 /******************tamplate*********************/ 31 struct Trie{ 32 int ch[26],num; 33 bool sign; 34 }T[N]; 35 int cnt=0,n,tot,pos[N]; 36 inline int id(char c){return c-'a';} 37 vector<int>v[N]; 38 void Insert(char *s,int num){ 39 int x=0,y; 40 D(i,strlen(s)-1,0){ 41 y=id(s[i]); 42 if (T[x].ch[y]==0) 43 T[x].ch[y]=++cnt; 44 x=T[x].ch[y]; 45 } 46 T[x].sign=1; 47 v[x].pb(num); 48 pos[num]=x; 49 } 50 int st[N],ed[N],a[N]; 51 void dfs(int x){ 52 st[x]=tot+1; 53 rep(i,v[x].size()) a[++tot]=v[x][i]; 54 rep(i,26) 55 if (T[x].ch[i]!=0) 56 dfs(T[x].ch[i]); 57 ed[x]=tot; 58 } 59 struct Tree{ 60 int cnt,l,r; 61 }t[N*30]; 62 int root[N],num=0; 63 #define mid (l+r>>1) 64 void update(int &o,int l,int r,int pos){ 65 t[++num]=t[o], o=num, ++t[o].cnt; 66 if (l==r) return; 67 if (pos<=mid) update(t[o].l,l,mid,pos); 68 else update(t[o].r,mid+1,r,pos); 69 } 70 int query(int i,int j,int rank){ 71 i=root[i]; j=root[j]; 72 int l=1,r=n; 73 while(l!=r){ 74 if (t[t[j].l].cnt-t[t[i].l].cnt>=rank) 75 r=mid,i=t[i].l,j=t[j].l; 76 else{ 77 rank-=t[t[j].l].cnt-t[t[i].l].cnt; 78 l=mid+1,i=t[i].r,j=t[j].r; 79 } 80 } 81 if (t[j].cnt-t[i].cnt<rank) return -1; 82 return l; 83 } 84 #undef mid 85 char s[N]; 86 int main(){ 87 #ifndef ONLINE_JUDGE 88 freopen("3439.in","r",stdin); 89 freopen("3439.out","w",stdout); 90 #endif 91 n=getint(); 92 F(i,1,n){ 93 scanf("%s",s); 94 Insert(s,i); 95 } 96 dfs(0); 97 #ifdef debug 98 F(i,1,tot) printf("%d ",a[i]);puts(""); 99 F(i,1,n) printf("%d %d ",st[pos[i]],ed[pos[i]]); 100 #endif 101 F(i,1,tot){ 102 root[i]=root[i-1]; 103 update(root[i],1,n,a[i]); 104 } 105 #ifdef debug 106 F(i,1,num) printf("%d %d %d %d ",i,t[i].l,t[i].r,t[i].cnt); 107 #endif 108 int k; 109 F(i,1,n){ 110 k=getint(); 111 int ans=query(st[pos[i]]-1,ed[pos[i]],k); 112 // printf("%d ",ans>n ? -1 : ans); 113 printf("%d ",ans); 114 } 115 return 0; 116 }
3439: Kpm的MC密码
Time Limit: 15 Sec Memory Limit: 256 MBSubmit: 234 Solved: 113
[Submit][Status][Discuss]
Description
背景
想Kpm当年为了防止别人随便进入他的MC,给他的PC设了各种奇怪的密码和验证问题(不要问我他是怎么设的。。。),于是乎,他现在理所当然地忘记了密码,只能来解答那些神奇的身份验证问题了。。。
描述
Kpm当年设下的问题是这样的:
现在定义这么一个概念,如果字符串s是字符串c的一个后缀,那么我们称c是s的一个kpm串。
系统将随机生成n个由a…z组成的字符串,由1…n编号(s1,s2…,sn),然后将它们按序告诉你,接下来会给你n个数字,分别为k1…kn,对于每 一个ki,要求你求出列出的n个字符串中所有是si的kpm串的字符串的编号中第ki小的数,如果不存在第ki小的数,则用-1代替。(比如说给出的字符 串是cd,abcd,bcd,此时k1=2,那么”cd”的kpm串有”cd”,”abcd”,”bcd”,编号分别为1,2,3其中第2小的编号就是 2)(PS:如果你能在相当快的时间里回答完所有n个ki的查询,那么你就可以成功帮kpm进入MC啦~~)
Input
第一行一个整数 n 表示字符串的数目
接下来第二行到n+1行总共n行,每行包括一个字符串,第i+1行的字符串表示编号为i的字符串
接下来包括n行,每行包括一个整数ki,意义如上题所示
Output
包括n行,第i行包括一个整数,表示所有是si的kpm串的字符串的编号中第ki小的数
Sample Input
3
cd
abcd
bcd
2
3
1
Sample Output
-1
2
样例解释
“cd”的kpm 串有”cd”,”abcd”,”bcd”,编号为1,2,3,第2小的编号是
2,”abcd”的kpm串只有一个,所以第3小的编号不存在,”bcd”的kpm
串有”abcd”,”bcd”,第1小的编号就是2。
数据范围与约定
设所有字符串的总长度为len
对于100%的数据,1<=n<=100000,0