(CodeChef) Chef and Strings

                                                                  厨师和字符串
题目描述
        大厨有一个长度为 N 的仅包含小写字符的字符串S,他同时准备了一个包含了所有S的子串的列表工。
        现在他向你询问了Q个询问,第i个询问形如,你需要,你需要在L中选择恰好k_{i}个相同的字符串。答案可能很大,输出模10^{9}+7后的结果。
输入格式
        输入数据的第一行包含一个整数 T,表示数据组数。对于每组测试数据,第一行包含两个整数 N,Q。接下来的一行,包含一个长度为 N 的字符串 S。接下来的Q行,每行包含一个整数 k_{i} 表示一组询问。
输出格式
        对于每组询问,输出一行表示对应的答案。

输入范围

        1\leq T\leq 1

        1\leq N\leq 5000

        1\leq Q\leq 10^{5}

        1\leq k_{i}\leq 10^{9}

题目分析

        1.相同的子字符串的数量最多仅为N,因为最小的子字符串为单个字符,单个字符最多有N个,即使所有的字符都相同,也仅有N个,所以可以把问题直接缩小到1\leq k_{i}\leq 5000

        2.根据题目上写的取模,说明这道题的答案会及其大,不能通过枚举求解.正确方针是求出相同子字符串的数量,再用组合数,求出k_{i}个相同的答案.例如此时有7个子字符串相同,k_{i}为2时,等于C(7,2),

k_{i}为3时,等于C(7,3),以此类推.

        3.子字符串可能各不相同,所以要分别统计每个子字符串相同的数量.

一种方法是把所有的子字符串记录下来,然后快排一遍,所有的子字符串数量是n(n+1)/2,空间复杂度是O(n^2),时间复杂度是O(n^2log(n)),原题目限制很小,这样可能能跑的过去,作者没试,只是云,TLE了我不背锅(

第二种方法所耗的空间和时间都挺小的.不考虑组合数算法的情况下空间O(n),时间O(n^2)

我们试想一下,要子字符串相同,例如存在一个s_{i}\sim s_{i+l}等于s_{j}\sim s_{j+l}    i\geq j.此时将s_{1}\sim s_{i+l}字符串倒转,一定有一个位置的z函数大于等于l(想一想为什么是大于等于而不是等于)

因此我们可以通过这种方式来找相同的子字符串.枚举结尾,然后将字符串倒转进行z函数操作.

有两点要注意,第一点,计算完z函数后,要前缀和的从后往前统计相同子字符串长度.

原因是如果某一串跟串头有k个相同的,那么一定代表(1~k-1)都是相同的,都要统计进去.

第二点,要记录一个vis数组,维护索引为i时,它的z函数最大值为多少.在之后统计相同子字符串长度时,要大于vis[i],因为小于等于vis[i]的说明已经跟以前某个字符串匹配过,计算过了,不能再统计.

#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll ans[5005],k[5005],c[5005][5005],vis[5005],z[5005];
const ll mod=1e9+7;
void calc(string&s){
    int n=s.size();
    for (int i=1,l=0,r=0;i<n;i++){
        if (i<=r&&z[i-l]<r-i+1){
            z[i]=z[i-l];
        }
        else{
            z[i]=max(0,r-i+1);
            while (z[i]<n&&s[i+z[i]]==s[z[i]]) ++z[i];
        }
        if (i+z[i]-1>r) l=i,r=i+z[i]-1;
        if (vis[n-i]<z[i]){
            k[z[i]]++;
            vis[n-i]=z[i];
        }
    }
}
void solve(){
    memset(ans,0,sizeof ans);
    memset(vis,0,sizeof vis);
    int n,q;
    cin>>n>>q;
    string str;
    cin>>str;
    for (int i=n;i>=1;i--){
        memset(k,0,sizeof k);
        string s=str.substr(0,i);
        reverse(s.begin(),s.end());
        calc(s);
        k[i]=1;
        for (int p=i;p>vis[i];p--){
            k[p]+=k[p+1];
            for (int j=1;j<=k[p];j++){
                if (k[p]>=j) ans[j]=(ans[j]+c[k[p]][j])%mod;
            }
        }
    }
    for (int i=1;i<=q;i++){
        int x;
        cin>>x;
        if (x<=n) cout<<ans[x]<<"\n";
        else cout<<0<<"\n";
    }
}
int main(){
    for (int i=0;i<=5000;i++) c[i][0]=1;
    for (int i=1;i<=5000;i++){
        for (int j=1;j<=i;j++){
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
        }
    }
    int t;
    cin>>t;
    while (t--){
        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值