链接大合集:
单纯地纪念一下写的第一份5K代码。。。/躺尸
因为ZJOI都不会所以只好写NOI的题了。。。
总之字符串题肯定一上来就拼个大字符串跑后缀数组啦!
(为了便于说明,放一下样例的sa)
1 #sbape#sgepe 2 #sgepe 3 #smape#sbape#sgepe 4 amgepe#smape#sbape#sgepe 5 ape#sbape#sgepe 6 ape#sgepe 7 bamgepe#smape#sbape#sgepe 8 bape#sgepe 9 cbamgepe#smape#sbape#sgepe 10 e 11 e#sbape#sgepe 12 e#sgepe 13 e#smape#sbape#sgepe 14 epe 15 epe#smape#sbape#sgepe 16 gepe 17 gepe#smape#sbape#sgepe 18 mape#sbape#sgepe 19 mgepe#smape#sbape#sgepe 20 pe 21 pe#sbape#sgepe 22 pe#sgepe 23 pe#smape#sbape#sgepe 24 sbape#sgepe 25 scbamgepe#smape#sbape#sgepe 26 sgepe 27 smape#sbape#sgepe
首先,对于每个T,考虑求出其子串总数。对于T每个后缀[i...|T|],用st表求该后缀和它前面第一个T的后缀的lcp,则这个后缀的有效子串即为[i...j](j属于[i+lcp,|T|])。
(对于"sgepe"的后缀"epe",lcp(10,14)=1,因此,其有效子串为"ep","epe")
然后,我们再考虑去掉其有效子串中S[l...r]中的部分。
考虑64pts的部分分,即l=1,r=n。对于T的后缀[i...|T|],只要求出L=max{lcp( T[i...|T|] , S[j...|S|] )},我们就能算出每个后缀对答案的贡献。可以发现,这两个部分分别在区间上取min和max,即分别递增、递减,因此我们只要找到两个函数值最靠近的位置即可。
考虑100pts,我们可以离线做。按询问的l从大到小排序,每次在线段树上加入新的字符串使得线段树中的字符串为S[i...|S|](i>=l)。显然,i>r的字符串并不会对答案产生影响,于是我们就可以愉快地线段树上二分啦!(二分需要找到最近的满足递减函数小于递增函数的位置,在线段树上需要先上去再下来,总之写起来很恶心就对了。。。写了个人不人鬼不鬼的zkw线段树(好吧其实我并不怎么会zkw线段树。。。)总之这就是长达5k的原因。。。)
(啊,代码真丑。)
(不知道是不是因为大家都是sam而我是sa所以这么慢。。。)
(sa:所以自己常数大反而怪我咯?←_←)
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 #define rep(i,l,r) for(int i=l;i<=r;++i) 5 #define per(i,r,l) for(int i=r;i>=l;--i) 6 #define pb push_back 7 #define gc getchar() 8 #define pc putchar 9 10 typedef pair<int,int> pii; 11 typedef long long ll; 12 13 const int N=2e6+3; 14 15 int rd(){ 16 int x=0;char c=gc; 17 while(c<'0'||c>'9') c=gc; 18 while(c>='0'&&c<='9'){ 19 x=x*10+c-'0'; 20 c=gc; 21 } 22 23 return x; 24 } 25 26 void prt(ll x){ 27 if(x>9) prt(x/10); 28 pc(x%10+'0'); 29 } 30 31 //Q 查询字符串 l-r/r-l 32 int L,n,m,sa[N]; 33 char s[N]; 34 35 namespace Sa{ 36 const int V=21; 37 int ht[N],ar[2][N],*rk,*tp,ct[N],mx,ln,st[V+1][N],lg[N]; 38 39 void get_sa(){ 40 rk=ar[0];tp=ar[1]; 41 42 rep(i,1,n) ct[s[i]]++; 43 rep(i,1,233) ct[i]+=ct[i-1]; 44 rep(i,1,n) sa[ct[s[i]]--]=i; 45 rep(i,1,n){ 46 if(s[sa[i]]!=s[sa[i-1]]) ++mx; 47 rk[sa[i]]=mx; 48 } 49 50 for(int k=1;mx<n;k<<=1){ 51 ln=0; 52 rep(i,n-k+1,n) tp[++ln]=i; 53 rep(i,1,n) if(sa[i]>k) tp[++ln]=sa[i]-k; 54 55 rep(i,1,mx) ct[i]=0; 56 rep(i,1,n) ct[rk[i]]++; 57 rep(i,1,mx) ct[i]+=ct[i-1]; 58 per(i,n,1) sa[ct[rk[tp[i]]]--]=tp[i]; 59 60 swap(tp,rk); 61 mx=0; 62 rep(i,1,n){ 63 int I=sa[i],J=sa[i-1]; 64 if(tp[I]!=tp[J]||tp[min(I+k,n+1)]!=tp[min(J+k,n+1)]) ++mx; 65 rk[I]=mx; 66 } 67 } 68 } 69 70 void get_ht(){ 71 rep(i,2,n) lg[i]=lg[i>>1]+1; 72 73 rep(i,1,n){//ht[rk[i]] 74 if(rk[i]==1) continue; 75 int v=ht[rk[i-1]];if(v>0) v--; 76 int J=sa[rk[i]-1]; 77 while(s[i+v]==s[J+v]) v++; 78 ht[rk[i]]=v; 79 } 80 81 rep(i,1,n) st[0][i]=ht[i]; 82 rep(j,1,V) rep(i,1,n) 83 st[j][i]=min(st[j-1][i],st[j-1][min(i+(1<<(j-1)),n)]); 84 } 85 86 void init(){ 87 get_sa(); 88 get_ht(); 89 } 90 91 int Q(int l,int r){ 92 if(l>r) swap(l,r); 93 if(l==r) return L+1; 94 if(r>n) return 0; 95 l++; 96 int j=lg[r-l+1]; 97 return min(st[j][l],st[j][r-(1<<j)+1]); 98 } 99 } 100 using Sa::Q; 101 using Sa::rk; 102 103 namespace Tr{ 104 const int M=(1<<22)-1,H=(1<<21)-1; 105 int mi[M+1],zuo[M+1],you[M+1]; 106 107 void build(){ 108 rep(i,H+1,M){ 109 zuo[i]=you[i]=i-H; 110 mi[i]=L+1; 111 } 112 per(i,H,1){ 113 zuo[i]=zuo[i<<1]; 114 you[i]=you[i<<1|1]; 115 mi[i]=L+1; 116 } 117 } 118 119 void cg(int x,int val){ 120 x+=H;mi[x]=val; 121 while(x!=1){ 122 x>>=1; 123 mi[x]=min(mi[x],val); 124 } 125 } 126 127 int rt,ctr; 128 129 bool ck(int miv,int pos){ 130 return (rt+1)<Q(pos,ctr)+miv; 131 //即 (rt-miv+1)<Q(pos,ctr) 132 //即 递减函数小于递增函数 133 } 134 135 int calc(int x){ 136 if(x==ctr) return 0; 137 return min(max(rt-mi[x+H]+1,0),Q(ctr,x)); 138 } 139 140 int get_mi(int x,int oi){//oi=0 oi=1 141 while((x<<1)<M){ 142 if(mi[x<<1|oi]<=rt) x=(x<<1)|oi; 143 else x=(x<<1)|(oi^1); 144 } 145 return x; 146 } 147 148 149 int Qr(int R,int C){//修改rt ctr 150 rt=R;ctr=C; 151 152 153 int ans=0,x,miv,pos; 154 155 x=ctr+H;miv=L+1; 156 while(x!=1){ 157 x>>=1; 158 if(x&1) continue; 159 if(ck(min(miv,mi[x|1]),you[x|1])) miv=min(mi[x|1],miv); 160 else{ 161 x|=1; 162 break; 163 } 164 } 165 while((x<<1)<M){ 166 int ls=(x<<1); 167 if(ck(min(miv,mi[ls]),you[ls])){ 168 miv=min(miv,mi[ls]); 169 x=(x<<1|1); 170 } 171 else x=ls; 172 } 173 pos=x-H; 174 175 176 ans=max(calc(pos),ans); 177 //往左的第一个 mi[pos]<=rt 178 while(x!=1){ 179 if(x&1){ 180 if(mi[x^1]<=rt){ 181 x=x^1; 182 break; 183 } 184 } 185 x>>=1; 186 } 187 pos=get_mi(x,1)-H; 188 ans=max(calc(pos),ans); 189 190 x=ctr+H;miv=L+1; 191 while(x!=1){ 192 x>>=1; 193 if(x&1){ 194 if(ck(min(miv,mi[x^1]),zuo[x^1])) miv=min(miv,mi[x^1]); 195 else{ 196 x^=1; 197 break; 198 } 199 } 200 } 201 while((x<<1)<M){ 202 int rs=(x<<1|1); 203 if(ck(min(miv,mi[rs]),zuo[rs])){ 204 miv=min(miv,mi[rs]); 205 x=(x<<1); 206 } 207 else x=rs; 208 } 209 pos=x-H; 210 211 ans=max(calc(pos),ans); 212 //往右第一个 213 while(x!=1){ 214 if((x&1)==0){ 215 if(mi[x|1]<=rt){ 216 x|=1; 217 break; 218 } 219 } 220 x>>=1; 221 } 222 pos=get_mi(x,0)-H; 223 224 ans=max(calc(pos),ans); 225 226 return ans; 227 } 228 } 229 using Tr::cg; 230 using Tr::Qr; 231 232 struct Query{ 233 int l,r,no; 234 }a[N]; 235 bool cmp(Query aa,Query bb){ 236 return aa.l>bb.l; 237 } 238 vector<int> ps[N]; 239 int hd[N],no[N],bg[N]; 240 241 typedef long long ll; 242 ll ans[N]; 243 244 int main(){ 245 scanf("%s",s+1); 246 L=n=strlen(s+1); 247 248 m=rd(); 249 rep(i,1,m){ 250 s[++n]='#'; 251 scanf("%s",s+1+n); 252 bg[i]=n; 253 n+=strlen(s+1+n); 254 255 rep(j,bg[i]+1,n) hd[j]=i,no[j]=n-j+1; 256 a[i]=(Query){rd(),rd(),i}; 257 } 258 259 Sa::init(); 260 261 rep(i,1,n){ 262 rep(j,sa[i],n) cerr<<s[j]; 263 cerr<<endl; 264 } 265 266 rep(i,1,n) ps[hd[sa[i]]].pb(i); 267 268 sort(a+1,a+1+m,cmp); 269 Tr::build(); 270 int R=L; 271 rep(i,1,m){ 272 while(a[i].l<=R) cg(rk[R],R) ,R--; 273 rep(j,0,ps[a[i].no].size()-1){ 274 int lcp=0,pos=ps[a[i].no][j]; 275 if(j) lcp=Q(pos,ps[a[i].no][j-1]); 276 lcp=max(lcp,Qr(a[i].r,pos)); 277 278 if(no[sa[pos]]>lcp) ans[a[i].no]+=no[sa[pos]]-lcp; 279 } 280 } 281 282 rep(i,1,m) printf("%lld ",ans[i]); 283 return 0; 284 } 285 //得到sa ht st存ht 286 //vector存每个询问的位置 287 288 //l从大到小对询问排序 289 //处理T的重复子串 290 //线段树加点 线段树上二分最小值