• CF645E Intellectual Inquiry


    洛谷传送门 CF传送门

    正好学校考试考到过一个加强版,写一写。

    Solution

    很明显的DP

    如果 (n=0) ,设 (f_i) 表示到 (i) 位置之前不同的子序列数,可以得到两个转移方程:

    1. (i) 位的字符之前没有出现过,方程就是 (f_i=2 imes f_{i-1}+1)

      意思是有 (i-1) 位时的方案,和 ({i-1}) 位的每一种方案都加第 (i) 位上的字符,还有自己 (1)

    2. (i) 位的字符出现过,方程是 (f_{i}=2 imes f_{i-1}-f_{pre_{a_i}})

      其中 (pre_{a_i}) 是第 (i) 位字符上次出现的位置,这个方程的意思是要减去上次这个字符的贡献

    现在 (n>0) ,因为要最大数量,所以第二个方程中 (f_{pre_{a_i}}) 减的越少越好,那么填的时候按 (pre_{a_i}) 从小到大依次填入最优

    此时的复杂度是 (O(n+m)) ,完全足够通过本题


    但是,如果是 (nleq 10^{18})

    这个时候,上面的方法就完全不行了

    我们发现在填 (n) 的时候,每 (k) 位都算是一个循环节,而 (kleq 26)

    我们可以考虑用一些和 (k) 有关且能快速求出答案的方法——矩阵快速幂

    将矩阵初始化的时候考虑第 (i) 个字符和第 (j) 个字符之间的相互影响即可

    时间复杂度: (O(m+k^3log n))

    注意:运算的时候没加空集,输出答案的时候记得加上

    Code

    #include<bits/stdc++.h>
    #define ll long long
    
    using namespace std;
    const int mod=1e9+7,N=4e6+10;
    int m,k,a[N],vis[N],q[N],cnt,f[N],pre[N],tot,pow2[N];
    ll n,t,ans,d;
    char s[N];
    
    inline int add(int x,int y){return x+y>mod?x+y-mod:x+y;}
    inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
    
    struct matrix{
        int c[210][210];
        void init(int d=0){
            for(int i=1;i<=k+1;i++)
                for(int j=1;j<=k+1;j++)
                    c[i][j]=0;
            for(int i=1;i<=k+1;i++) c[i][i]=d;
        }
        matrix operator * (matrix x){
            matrix res;
            res.init();
            for(int i=1;i<=k+1;i++)
                for(int j=1;j<=k+1;j++)
                    for(int l=1;l<=k+1;l++)
                        res.c[i][j]=add(res.c[i][j],1ll*c[i][l]*x.c[l][j]%mod);
            return res;
        }
    }Ans;
    
    matrix Fpow(matrix a,ll b){
        matrix res;
        res.init(1);
        while(b){
            if(b&1) res=res*a;
            a=a*a;
            b>>=1;
        }
        return res;
    }
    
    int main(){
        scanf("%lld%d",&n,&k);cnt=k;
        scanf("%s",s+1);
        m=strlen(s+1);
        for(int i=1;i<=m;i++)
            a[i]=s[i]-'a'+1;
        for(int i=m;i>=1;i--)
            if(!vis[a[i]]) q[--cnt]=a[i],vis[a[i]]=1;
        for(int i=1;i<=k;i++)
            if(!vis[i]) q[--cnt]=i;
        memset(pre,-1,sizeof(pre));
        t=min(n,1ll*k);
        for(int i=1;i<=m+t;i++){
            if(i>m) a[i]=q[tot],tot=(tot+1)%k;
            if(pre[a[i]]!=-1) f[i]=dec(add(f[i-1],f[i-1]),f[pre[a[i]]-1]);
            else f[i]=add(add(f[i-1],f[i-1]),1);
            pre[a[i]]=i;
        }
        if(n==t){
            printf("%d
    ",f[n+m]+1);
            return 0;
        }
        Ans.init();
        pow2[0]=1;
        for(int i=1;i<=k+1;i++) pow2[i]=pow2[i-1]*2%mod;
        Ans.c[1][k+1]=1;
        for(int i=2;i<=k+1;i++){
            for(int j=1;j<i-1;j++)
                Ans.c[i][j]=dec(mod,pow2[i-j-1]);
            Ans.c[i][i-1]=mod-1;
            Ans.c[i][k+1]=add(Ans.c[i][k+1],pow2[i-1]);
        }
        d=(n-1)/k,n-=d*k;
        Ans=Fpow(Ans,d);
        for(int i=0;i<=k;i++)
            ans=add(ans,1ll*f[m+i]*Ans.c[n+1][i+1]%mod);
        printf("%d
    ",ans+1);
        return 0;
    }
    
  • 相关阅读:
    基于NIO的同步非阻塞编程完整案例,客户端发送请求,服务端获取数据并返回给客户端数据,客户端获取返回数据
    NIO编程中buffer对象的理解以及API的使用
    使用简单工厂模式写一个简单的计算器!!!
    java数字转字符串前面自动补0或者其他数字
    jQuery Validate自定义金钱验证,是否为金额格式,保留两位小数,并支持千分制货币格式
    javade多任务处理之Executors框架(线程池)实现的内置几种方式与两种基本自定义方式
    【转】asp.net mvc webapi+angular.js案例
    【转】MVC5中的区域(Areas)
    【转】在ASP.NET MVC中,使用Bundle来打包压缩js和css
    scroll pagination.js数据重复加载、分页问题
  • 原文地址:https://www.cnblogs.com/jasony/p/13872579.html
Copyright © 2020-2023  润新知