某题
后缀数组
题目描述
给定一个字符串S,它的长为n,后缀数组的功能是,将其所有后缀按字典序从小到大排好序。我们对其做一点小小的改动:再给定一个数字m,记ssi表示从S的第i位开始、长度最多为m的子串,我们希望将这些字符串{ssi}按字典序从小到大排序。举个栗子,当S="abcab",m=2时,ssi的值分别为:
ss1="ab"
ss2="bc"
ss3="ca"
ss4="ab"
ss5="b"
但是,只是把{ssi}全部排好序还是太简单了。初始状态下,ss1~ssn按顺序排成一行,我们只能通过不断交换某两个相邻字符串的位置来做排序。再举个栗子,把上面提到的ss1~ss5排好序的一种方案是:
(0)原序列:"ab", "bc", "ca", "ab", "b"
(1)交换第3和第4个串:"ab", "bc", "ab", ca", "b"
(2)交换第2和第3个串:"ab", "ab", "bc", ca", "b"
(3)交换第4和第5个串:"ab", "ab", "bc", b", "ca"
(4)交换第3和第4个串:"ab", "ab", "b", bc", "ca"
现在,你需要求出,最少通过多少次相邻字符串交换,才能把所有子串{ssi}排成字典序从小到大的形式。
( ´_ゝ`)NOIP怎么可能会考后缀数组
输入格式
第一行包含两个整数n和m;
第二行包含字符串S,它的长为n,只包含小写字母。
输出格式
一个整数,表示最少交换次数。
样例输入
5 2
abcab
样例输出
4
样例解释
样例就是题目描述中提到的例子。
数据范围
对于20%的数据,有n≤10;
对于40%的数据,有n≤100;
对于60%的数据,有n≤5000;
另有10%的数据,有m≤5;
另有10%的数据,S是随机生成的;
对于100%的数据,有1≤m≤n≤50000
这是某道考试题
考场上写的是乱搞\((O(nlog(n)))\)一个类似平衡树(权值树状数组或权值线段树)的做法
但是因为某STL容器的巨大常数和存储字符串所需的巨大空间
导致TLE(MLE)
然后正解hash二分字符串判等和归并排序
所以不会出现MLE这种问题
然后我就想,存储字符串用Trie树不就好了,而且需要动态开点
否则会MLE的更惨
然后我就YY了一个做法
然后就写完了
然后还是TLE和MLE……
其实TLE是很好解决的,在每个节点开一棵线段树或者平衡树维护一下就好
但是MLE好像不太好解决
因为每个节点都有26个子节点
空间开销非常大
虽然由DAT这种特殊的"Trie"树
每个节点只需要开2个值维护
但是如何维护子节点的信息是个难题(TLE)
而且问题是我不会用啊23333
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cstdio>
#define Max 26
using namespace std;
struct TrieNode{
int ti;
TrieNode *next[Max];
TrieNode(){
ti=0;
memset(next,NULL,sizeof(next));
}
};
void Insert(TrieNode *root,char * str){
int len=strlen(str);
TrieNode *p=root;
for(int i=0;i<len;++i){
int id=str[i]-'a';
if(p->next[id]==NULL)
p->next[id]=new TrieNode();
p=p->next[id];
p->ti++;
}
}
int searchTrie(TrieNode *root,char *str){
TrieNode *p=root;
int len=strlen(str);
for(int i=0;i<len;++i){
int id=str[i]-'a';
if(p->next[id]==NULL)
return 0;
p=p->next[id];
}
return p->ti;
}
int Behind(TrieNode *root,char *str){
TrieNode *p=root;
int len=strlen(str);
int ans=0;
for(int i=0;i<len;++i){
int id=str[i]-'a';
for(int j=id+1;j<Max;++j)
if(p->next[j]!=NULL)
ans+=p->next[j]->ti;
p=p->next[id];
}
for(int i=0;i<Max;++i)
if(p->next[i]!=NULL)
ans+=p->next[i]->ti;
return ans;
}
int n,m,ans;
char str[50005];
char now[50006];
int main(){
TrieNode *root=new TrieNode();
scanf("%d%d",&n,&m);
int sum=0,ans=0;
char ch=getchar();
scanf("%s",str);
for(int i=0;i<n;++i){
if(i+m>n-1){
int j=0;
for(int k=i;k<=n;++k,++j)
now[j]=str[k];
}
else for(int j=0,k=i;j<m;++k,++j)
now[j]=str[k];
Insert(root,now);
ans+=Behind(root,now);
}
printf("%d",ans);
delete root;
return 0;
}
用vector乱搞的(只能过6个测试点)
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<cctype>
#include<cstdio>
#include<string>
#include<cstdio>
#define N 50005
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
ch=getchar();
while(isdigit(ch))
x=x*10+ch-'0',ch=getchar();
return x*f;
}
vector<string>a[27];
void insert(string x,int i){
a[i].insert(upper_bound(a[i].begin(),a[i].end(),x),x);
return;
}
int find(string x,int i){
return upper_bound(a[i].begin(),a[i].end(),x)-a[i].begin();
}
long long ans;
int n,m;
string str;
int sum[N];
int main(){
n=read(),m=read();
cin>>str;
string now;
int len=str.size();
for(int i=0;i<len;++i){
if(i+m-1>len-1)
now=str.substr(i,len-i);
else now=str.substr(i,m);
int shou=now[0]-'a';
for(int j=shou;j<26;++j)ans+=sum[j];
ans-=find(now,shou);
insert(now,shou);
sum[shou]++;
}
cout<<ans;
return 0;
}
然后作死写了个可持久化Treap代替缓慢的vector
然后过了9个点(如果把内存开到512MB的话)
然后最后第八个数据点死活过不去(100s,950MB内存)
原因当然是因为string啦
但是有没有切实可行的能代替string的解决方案
我还真是不知道
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<cctype>
#include<cstdio>
#include<string>
#include<cstdio>
#define N 50005
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
ch=getchar();
while(isdigit(ch))
x=x*10+ch-'0',ch=getchar();
return x*f;
}
class Treap{
private:
int com,x,y,z,a,b;
int root;
int ch[N][3];
string val[N];
int pri[N];
int siz[N];
int sz;
void update(int x){
siz[x]=1+siz[ch[x][0]]+siz[ch[x][1]];
}
int new_node(string v){
siz[++sz]=1;
val[sz]=v;
pri[sz]=rand();
return sz;
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(pri[x]<pri[y]){
ch[x][1]=merge(ch[x][1],y);
update(x);
return x;
}
else{
ch[y][0]=merge(x,ch[y][0]);
update(y);
return y;
}
}
void split(int now,string k,int &x,int &y){
if(!now)x=y=0;
else{
if(val[now]<=k)
x=now,split(ch[now][1],k,ch[now][1],y);
else
y=now,split(ch[now][0],k,x,ch[now][0]);
update(now);
}
}
public:
Treap(){
root=0;
}
void Insert(string a){
split(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
}
int Find(string a){
split(root,a,x,y);
int ans=siz[x];
root=merge(x,y);
return ans;
}
};
int ans;
int n,m;
string str;
int sum[N];
Treap BBT[27];
int find(int a,string str){
return BBT[a].Find(str);
}
int main(){
n=read(),m=read();
cin>>str;
string now;
int len=str.size();
for(int i=0;i<len;++i){
if(i+m-1>len-1)
now=str.substr(i,len-i);
else now=str.substr(i,m);
int shou=now[0]-'a';
for(int j=shou;j<26;++j)ans+=sum[j];
ans-=find(shou,now);
BBT[shou].Insert(now);
sum[shou]++;
}
printf("%d\n",ans);
return 0;
}
但是所有被存储在平衡树中的字符串都是输进来的那个字符串的一个子串
然后对于这个题,我们就可以搞点事情
用一个结构体(pair)存储一个字符串在母串中的开端和结束
然后比较直接将字符串拖出来比较(反正暴力比较不会超时)
这样就省去了巨大的空间开销
但是这个东西并不能代替string
而且非常狗彘的是这个程序过不了第九和第十个数据
正好和string的相反string过不了第八个数据
原因很简单
第八个点是随机字符串
第九、十个点是相同的字符偏多
所以\(O(n)\)比较非常慢
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<cctype>
#include<cstdio>
#include<string>
#include<cstdio>
#define N 50005
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
ch=getchar();
while(isdigit(ch))
x=x*10+ch-'0',ch=getchar();
return x*f;
}
char str[N];
struct si{
int l,r;
};
int ii=0,jj=0;
bool QxHd(si a,si b){
ii=a.l,jj=b.l;
int lena=a.r-a.l+1,lenb=b.r-b.l+1;
for(register int k=0;k<min(lena,lenb);++k)
if(str[ii+k]<str[jj+k])return true;
else if(str[ii+k]==str[jj+k])continue;
else return false;
return lena<=lenb;
}
class Treap{
private:
int com,x,y,z,a,b;
int root;
int ch[N][3];
si val[N];
int pri[N];
int siz[N];
int sz;
void update(int x){
siz[x]=1+siz[ch[x][0]]+siz[ch[x][1]];
}
int new_node(si asd){
siz[++sz]=1;
val[sz]=asd;
pri[sz]=rand();
return sz;
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(pri[x]<pri[y]){
ch[x][1]=merge(ch[x][1],y);
update(x);
return x;
}
else{
ch[y][0]=merge(x,ch[y][0]);
update(y);
return y;
}
}
void split(int now,si &k,int &x,int &y){
if(!now)x=y=0;
else{
if(QxHd(val[now],k))
x=now,split(ch[now][1],k,ch[now][1],y);
else
y=now,split(ch[now][0],k,x,ch[now][0]);
update(now);
}
}
public:
Treap(){
root=0;
}
void Insert(si &a){
split(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
}
int Find(si &a){
split(root,a,x,y);
int ans=siz[x];
root=merge(x,y);
return ans;
}
};
int ans;
int n,m;
int sum[N];
Treap BBT[27];
int find(int a,si asd){
return BBT[a].Find(asd);
}
int main(){
n=read(),m=read();
scanf("%s",str);
si now;
int len=strlen(str);
for(int i=0;i<len;++i){
if(i+m-1>len-1)
now=si{i,len-1};
else now=si{i,i+m-1};
int shou=str[now.l]-'a';
for(int j=shou;j<26;++j)ans+=sum[j];
ans-=find(shou,now);
BBT[shou].Insert(now);
sum[shou]++;
}
printf("%d",ans);
return 0;
}
最后一个优化
将字符串比较器用hash二分比较优化成\(O(log(n))\)
然后就可以300MS左右通过这道题目了
但是我没有调出我的hash来……
正解没我快,hahaha
虽然我是全WA掉了
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<cctype>
#include<cstdio>
#include<string>
#include<cstdio>
#define N 50005
using namespace std;
int n,m;
int sum[N];
int hash[N];
int pow[N];
inline int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
ch=getchar();
while(isdigit(ch))
x=x*10+ch-'0',ch=getchar();
return x*f;
}
char str[N];
const int Mod=1e9+7;
bool QxHd(int la,int lb){
if(la==lb)return true;
int l,r,mid;
long long hsi,hsj;
l=0,r=m+1;
if(la+m-1>n-1)r=n-1;
if(lb+m-1>n-1)r=n-1;
while(l<r){
mid=(l+r)>>1;
hsi=(hash[la+mid-1]-hash[la-1]+Mod)%Mod;
hsj=(hash[lb+mid-1]-hash[lb-1]+Mod)%Mod;
if(hsi!=hsj)r=mid-1;
else l=mid+1;
}
if(l==m)return true;
return str[la+l]<=str[lb+l];
}
class Treap{
private:
int com,x,y,z,a,b;
int root;
int ch[N][3];
int val[N];
int pri[N];
int siz[N];
int sz;
void update(int x){
siz[x]=1+siz[ch[x][0]]+siz[ch[x][1]];
}
int new_node(int asd){
siz[++sz]=1;
val[sz]=asd;
pri[sz]=rand();
return sz;
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(pri[x]<pri[y]){
ch[x][1]=merge(ch[x][1],y);
update(x);
return x;
}
else{
ch[y][0]=merge(x,ch[y][0]);
update(y);
return y;
}
}
void split(int now,int k,int &x,int &y){
if(!now)x=y=0;
else{
if(QxHd(val[now],k))
x=now,split(ch[now][1],k,ch[now][1],y);
else
y=now,split(ch[now][0],k,x,ch[now][0]);
update(now);
}
}
public:
Treap(){
root=0;
}
void Insert(int &a){
split(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
}
int Find(int &a){
split(root,a,x,y);
int ans=siz[x];
root=merge(x,y);
return ans;
}
};
Treap BBT[27];
int ans;
int find(int a,int asd){
return BBT[a].Find(asd);
}
void Hash(char *st){
hash[0]=0;
pow[0]=1;
for(int i=0;i<n;++i){
hash[i+1]=(hash[i]*29+st[i]-'a')%Mod;
pow[i+1]=pow[i]*29%Mod;
}
}
int main(){
n=read(),m=read();
scanf("%s",str);
Hash(str);
int now;
int len=strlen(str);
for(int i=0;i<len;++i){
now=i;
int shou=str[now]-'a';
for(int j=shou;j<26;++j)ans+=sum[j];
ans-=find(shou,now);
BBT[shou].Insert(now);
sum[shou]++;
}
printf("%d",ans);
return 0;
}