LGP7046 「MCOI-03」诗韵 - Sol
Preface
很快想到个自然的解法,然后卡常了一个下午(其它解法应该没这么卡)。
::::info[卡常记录(部分)] ::::
思路和 [@if_miao]() 大佬类似的,选择用 SAM 与线段树合并解决这个东西。
Sol
题意就是对于每次新加入的区间,问有多少个子串满足是
我们建 SAM 后可以得到每个下标在 SAM 里对应的位置(记为
跳 link 用倍增预处理(记
然后考虑怎么计算答案,由于
同一 endpos 中,按长度总大到小处理(因为显然长度小的是长度大的后缀),由于重复子串只记一次,同一长度时贪心地只记录询问最靠前的那个区间,把它单点加入线段树中,这样如果线段树内可以查到第
比较显然的是累计答案我们不可能每个都加,这里差分一下就行(和前面贪心记最小下标配合,所以不会漏)。
这个做法是
然后另一个细节是询问的重复区间无效,在这个做法中,set<pair<int,int>>、map<pair<int,int>,bool> 都很慢,会 TLE 一个点,应该把询问排序后,重复的只保留下标靠前的。
::::success[AC Code]
#include <bits/stdc++.h>
#define zls rp++
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const ll inf=2e18;
const ll md=1e9+7;
const double eps=1e-12;
struct node{
int pre,len;
int nx[30];
}p[1000005];
int n,m,k,id=0,lst=0;
string s;
void add(int x){
int op=++id,l=lst;
p[op].len=p[l].len+1;
while(l!=-1&&!p[l].nx[x]){
p[l].nx[x]=op;
l=p[l].pre;
}
if(l==-1)p[op].pre=0;
else{
int v=p[l].nx[x];
if(p[v].len==p[l].len+1)p[op].pre=v;
else{
int cp=++id;
p[cp]=p[v];
p[cp].len=p[l].len+1;
while(l!=-1&&p[l].nx[x]==v){
p[l].nx[x]=cp;
l=p[l].pre;
}
p[op].pre=p[v].pre=cp;
}
}
lst=op;
}
int pos[500005],fa[25][1000005];
vector<int>e[1000005];
vector<pii>ques[1000005];
int rt[1000005],mx[500005],t=0;
ll ans[500005];
struct tree{
int ls,rs;
int sum;
}tr[30000005];
int merge(int lx,int rx,int l,int r){
if(!lx)return rx;
if(!rx)return lx;
if(l==r){
tr[lx].sum|=tr[rx].sum;
return lx;
}
int mid=(l+r)/2;
tr[lx].ls=merge(tr[lx].ls,tr[rx].ls,l,mid);
tr[lx].rs=merge(tr[lx].rs,tr[rx].rs,mid+1,r);
tr[lx].sum=tr[tr[lx].ls].sum+tr[tr[lx].rs].sum;
return lx;
}
int query(int x,int l,int r,int K){
if(tr[x].sum<K)return 0;
if(l==r)return l;
int mid=(l+r)/2,lsum=tr[tr[x].ls].sum;
if(lsum>=K)return query(tr[x].ls,l,mid,K);
else return query(tr[x].rs,mid+1,r,K-lsum);
}
int add(int x,int l,int r,int pos){
if(!x)x=++t;
if(l==r){
tr[x].sum|=1;
return x;
}
int mid=(l+r)/2;
if(pos<=mid)tr[x].ls=add(tr[x].ls,l,mid,pos);
else tr[x].rs=add(tr[x].rs,mid+1,r,pos);
tr[x].sum=tr[tr[x].ls].sum+tr[tr[x].rs].sum;
return x;
}
void dfs(int x){
for(int v:e[x])
dfs(v),rt[x]=merge(rt[x],rt[v],1,m);
auto &now=ques[x];
int pre=p[x].len,num=now.size();
sort(now.rbegin(),now.rend());//按长度倒序
for(int i=0;i<num;i++){
if(now[i+1].first==now[i].first)continue;
pii op=now[i];
int len=op.first,id=op.second;
int ok=query(rt[x],1,m,k+1);//开始贡献答案的位置
if(ok)
ans[ok]+=pre-len,
mx[ok]=max(mx[ok],pre);
rt[x]=add(rt[x],1,m,id);
pre=len;
}
int ok=query(rt[x],1,m,k+1);
if(ok)
ans[ok]+=(x==0?1:pre-p[p[x].pre].len),
mx[ok]=max(mx[ok],pre);
}
struct Q{
int l,r,id;
}q[500005];
void work(){
p[0].pre=-1;
cin>>n>>m>>k;
cin>>s;
for(int i=0;i<n;i++)
add(s[i]-'a'+1),pos[i+1]=lst;
for(int i=1;i<=id;i++)
fa[0][i]=p[i].pre,
e[p[i].pre].push_back(i);
for(int j=1;j<=19;j++)
for(int i=1;i<=id;i++)
fa[j][i]=fa[j-1][fa[j-1][i]];
for(int i=1;i<=m;i++)
cin>>q[i].l>>q[i].r,q[i].id=i;
sort(q+1,q+1+m,[&](Q A,Q B){
return A.l==B.l?(A.r==B.r?A.id<B.id:A.r<B.r):A.l<B.l;
});
for(int i=1;i<=m;i++){
if(q[i].l==q[i-1].l&&q[i].r==q[i-1].r)continue;
int l=q[i].l,r=q[i].r,op=pos[r];
for(int j=19;j>=0;j--)
if(fa[j][op]&&p[fa[j][op]].len>=r-l+1)
op=fa[j][op];
ques[op].push_back({r-l+1,q[i].id});
}
dfs(0);
for(int i=1;i<=m;i++)
ans[i]+=ans[i-1],mx[i]=max(mx[i],mx[i-1]),
cout<<ans[i]<<' '<<mx[i]<<'\n';
}
int main(){
//freopen("ansin.in","r",stdin);
//freopen("ansout.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
work();
return 0;
}
::::