https://acm.hdu.edu.cn/showproblem.php?pid=6988
题意:
给出一个字符串,每个字母都有一定的价值,子串的价值为各个字母价值总和
问所有不同的子串中,价值第k小的子串的价值是多少
首先二分一个价值,检验这个价值是否满足要求
一开始的二分写的 若小于等于二分值的子串数量<=k就更新答案,是错误的
应该是 若小于等于二分值的子串数量>=k就更新答案
因为可能存在多个不同的子串价值相同,不加他们不够k,加上他们之后超过k
如何求子串价值小于等于某个值的子串数量?
一个字符串的所有后缀的所有前缀就是所有的子串
如果没有不同子串的限制:
枚举后缀的起始位置,利用前缀和,二分可以求出这个后缀的多少个前缀价值<=二分的值
现在要求只计算不同的子串
利用后缀数组,按照rank从小到大算
因为排名相邻的两个串的最长公共前缀是height[i],也就是说排名为i的后缀的前height[i]个前缀都在排名为i-1的后缀里包含了
只需要对这个后缀二分的时候更改一下二分的下界即可
#include<bits/stdc++.h> using namespace std; #define N 100002 char ch[N]; int n,k,a[N],v[N],p,q,sa[2][N],rk[2][N],h[N]; long long m; int w[30],val[N]; void mul(int *sa,int *rk,int *SA,int *RK) { for(int i=1;i<=n;i++) v[rk[sa[i]]]=i; for(int i=n;i;i--) if(sa[i]>k) SA[v[rk[sa[i]-k]]--]=sa[i]-k; for(int i=n-k+1;i<=n;i++) SA[v[rk[i]]--]=i; for(int i=1;i<=n;i++) RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i]]!=rk[SA[i-1]]||rk[SA[i]+k]!=rk[SA[i-1]+k]); } void presa() { p=0; q=1; for(int i=1;i<=26;++i) v[i]=0; for(int i=1;i<=n;i++) v[a[i]]++; for(int i=1;i<=26;i++) v[i]+=v[i-1]; for(int i=1;i<=n;i++) sa[p][v[a[i]]--]=i; for(int i=1;i<=n;i++) rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]); for(k=1;k<n;k<<=1,swap(p,q)) mul(sa[p],rk[p],sa[q],rk[q]); for(int i=1,k=0;i<=n;i++) { int j=sa[p][rk[p][i]-1]; while(a[i+k]==a[j+k]) k++; h[rk[p][i]]=k;if(k) k--; } } long long check(int lim) { int l,r=n,mid,tmp; long long sum=0; for(int i=1;i<=n;++i) { l=sa[p][i]+h[i]; r=n; tmp=l-1; while(l<=r) { mid=l+r>>1; if(val[mid]-val[sa[p][i]-1]<=lim) { tmp=mid; l=mid+1; } else r=mid-1; } sum+=tmp-(sa[p][i]+h[i])+1; } return sum; } void solve() { for(int i=1;i<=n;++i) val[i]=val[i-1]+w[a[i]]; int l=0,r=1e7+1,mid,ans=-1; long long tmp; while(l<=r) { mid=l+r>>1; tmp=check(mid); if(tmp>=m) { ans=mid; r=mid-1; } else l=mid+1; } if(ans==1e7+1) ans=-1; printf("%d ",ans); } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); scanf("%s",ch+1); for(int i=1;i<=n;++i) a[i]=ch[i]-'a'+1; for(int i=1;i<=26;++i) scanf("%d",&w[i]); presa(); solve(); } }