BZOJ.4516.[SDOI2016]生成魔咒(后缀数组 RMQ)
后缀数组是可以做的: 本质不同的字符串的个数为 \(子串个数-\sum_{ht[i]}\),即 \(\frac{n(n+1)}{2}-\sum_{ht[i]}\).
如果是每次往后边插入字符,会改变SA[]。但如果向前边插入字符,相当于只加入了一个后缀。
所以离线,把原串反过来。
每次插入一个字符,即新增一个前缀i,它的贡献是\(len-max(lcp(pre,i),lcp(i,nxt))\),其中\(pre,nxt\)为与后缀\(i\)(当前)排名相邻的两个串。
用set维护当前的排名,RMQ求lcp。(懒得写\(O(n)\)的维护单调栈正反跑两遍)
做了几道后缀数组了,竟然真没写过lcp(也许是太简单了)。
\(10^5\)很容易爆int!
//13228kb 1000ms
#include <set>
#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
const int N=1e5+5,B=17;
int n,cnt,A[N],ref[N],tm[N],sa[N],sa2[N],rk[N],ht[N],log2[N],st[N][18];
std::set<int> set_rk;
std::set<int>::iterator it;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
int Find(int x)
{
int l=1,r=cnt,mid;
while(l<r)
if(ref[mid=l+r>>1]<x) l=mid+1;
else r=mid;
return l;
}
void Get_SA()
{
int *x=rk,*y=sa2,m=n+1;
for(int i=0; i<=m; ++i) tm[i]=0;
for(int i=1; i<=n; ++i) ++tm[x[i]=A[i]];
for(int i=1; i<=m; ++i) tm[i]+=tm[i-1];
for(int i=n; i; --i) sa[tm[x[i]]--]=i;
for(int k=1,p=0; k<n; k<<=1,m=p,p=0)
{
for(int i=n-k+1; i<=n; ++i) y[++p]=i;
for(int i=1; i<=n; ++i) if(sa[i]>k) y[++p]=sa[i]-k;
for(int i=0; i<=m; ++i) tm[i]=0;
for(int i=1; i<=n; ++i) ++tm[x[i]];
for(int i=1; i<=m; ++i) tm[i]+=tm[i-1];
for(int i=n; i; --i) sa[tm[x[y[i]]]--]=y[i];
std::swap(x,y), x[sa[1]]=p=1;
for(int i=2; i<=n; ++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p:++p;
if(p>=n) break;
}
for(int i=1; i<=n; ++i) rk[sa[i]]=i;
ht[1]=0;
for(int k=0,p,i=1; i<=n; ++i)
{
if(rk[i]==1) continue;
if(k) --k;
p=sa[rk[i]-1];
while(i+k<=n && p+k<=n && A[i+k]==A[p+k]) ++k;
ht[rk[i]]=k;
}
}
void Init_RMQ()
{
for(int i=1; i<=n; ++i) st[i][0]=ht[i];
log2[1]=0;
for(int i=2; i<=n; ++i) log2[i]=log2[i>>1]+1;
for(int j=1; j<=log2[n]; ++j)
for(int i=n-(1<<j-1); i; --i)
st[i][j]=std::min(st[i][j-1],st[i+(1<<j-1)][j-1]);
}
int LCP(int l,int r)
{
if(l>r) std::swap(l,r);
++l; int k=log2[r-l+1];
return std::min(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
n=read();
for(int i=1; i<=n; ++i) ref[i]=A[i]=read();
std::sort(ref+1,ref+1+n), cnt=1;
for(int i=2; i<=n; ++i) if(ref[i]!=ref[i-1]) ref[++cnt]=ref[i];
for(int i=1; i<=n; ++i) A[i]=Find(A[i]);
// for(int i=1,lim=n>>1; i<=lim; ++i) std::swap(A[i],A[n-i+1]);//惊了 还写错了== 还是写reverse吧
std::reverse(A+1,A+1+n);
Get_SA(), Init_RMQ();
long long res=0;
set_rk.insert(0);
for(int tmp,i=n; i; --i)//从头开始加入字符(前缀)。
{
tmp=0, it=set_rk.upper_bound(rk[i]);
if(it!=set_rk.end()) tmp=LCP(rk[i],*it);
if((--it)!=set_rk.begin()) tmp=std::max(tmp,LCP(*it,rk[i]));
res+=n-i+1-tmp, set_rk.insert(rk[i]);
printf("%lld\n",res);
}
return 0;
}
Hash暴力:(60)
那个暴力后缀数组理论也有60啊但是只有20。。懒得看了
#include <set>
#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
#define Enter putchar('\n')
typedef unsigned long long ull;
const ull seed=131;
const int N=2007;
int n,cnt,A[100005],ref[100005],tm[N],sa[N],sa2[N],rk[N],ht[N];
ull pw[N];
std::set<ull> st;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
int Find(int x)
{
int l=1,r=cnt,mid;
while(l<r)
if(ref[mid=l+r>>1]<x) l=mid+1;
else r=mid;
return l;
}
void Get_SA(int n)
{
int *x=rk,*y=sa2,m=n;//std::min(cnt,n)+2;
for(int i=0; i<=m; ++i) tm[i]=0;
for(int i=1; i<=n; ++i) ++tm[x[i]=A[i]];
for(int i=1; i<=m; ++i) tm[i]+=tm[i-1];
for(int i=n; i; --i) sa[tm[x[i]]--]=i;
for(int k=1,p=0; k<n; k<<=1,m=p,p=0)
{
for(int i=n-k+1; i<=n; ++i) y[++p]=i;
for(int i=1; i<=n; ++i) if(sa[i]>k) y[++p]=sa[i]-k;
for(int i=0; i<=m; ++i) tm[i]=0;
for(int i=1; i<=n; ++i) ++tm[x[i]];
for(int i=1; i<=m; ++i) tm[i]+=tm[i-1];
for(int i=n; i; --i) sa[tm[x[y[i]]]--]=y[i];
std::swap(x,y), x[sa[1]]=p=1;
for(int i=2; i<=n; ++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?p:++p;
if(p>=n) break;
}
for(int i=1; i<=n; ++i) rk[sa[i]]=i;
ht[1]=0;
for(int k=0,p,i=1; i<=n; ++i)
{
if(rk[i]==1) continue;
if(k) --k;
p=sa[rk[i]-1];
while(i+k<=n && p+k<=n && A[i+k]==A[p+k]) ++k;
ht[rk[i]]=k;
}
// puts("Output:");
// for(int i=1; i<=n; ++i) printf("SA:No:%d pos:%d\n",i,sa[i]);Enter;
// for(int i=1; i<=n; ++i) printf("RK:%d rank:%d\n",i,rk[i]);Enter;
// for(int i=1; i<=n; ++i) printf("HT:%d ht:%d\n",i,ht[i]);Enter;
int res=n-sa[1]+1;
for(int i=2; i<=n; ++i) res+=n-sa[i]+1-ht[i];
printf("%d\n",res);
}
void N_2(){
for(int i=1; i<=n; ++i) Get_SA(i);
}
void HASH(){
pw[0]=1;
for(ull i=1; i<=n; ++i) pw[i]=pw[i-1]*i;
int res=0;
for(int p=1; p<=n; ++p)
{
ull hs=0;
for(int i=p; i; --i)
{
hs=hs*seed+A[i];
if(!st.count(hs)) ++res,st.insert(hs);
}
printf("%d\n",res);
}
}
int main()
{
freopen("incantation.in","r",stdin);
freopen("incantation.out","w",stdout);
n=read();
for(int i=1; i<=n; ++i) ref[i]=A[i]=read();
std::sort(ref+1,ref+1+n), cnt=1;
for(int i=2; i<=n; ++i) if(ref[i]!=ref[i-1]) ref[++cnt]=ref[i];
for(int i=1; i<=n; ++i) A[i]=Find(A[i]);
if(n<=2000) {HASH(); return 0;}
if(n<=2000) {N_2(); return 0;}//mmp
for(int i=1; i<=n; ++i) printf("%d\n",i);
fclose(stdin);fclose(stdout);
return 0;
}
------------------------------------------------------------------------------------------------------------------------
很久以前的奇怪但现在依旧成立的签名
attack is our red sun $$\color{red}{\boxed{\color{red}{attack\ is\ our\ red\ sun}}}$$ ------------------------------------------------------------------------------------------------------------------------
很久以前的奇怪但现在依旧成立的签名
attack is our red sun $$\color{red}{\boxed{\color{red}{attack\ is\ our\ red\ sun}}}$$ ------------------------------------------------------------------------------------------------------------------------