2019牛客暑期多校训练营(第五场)基础DP+组合数 矩阵快速幂

  1. G题:
    题目链接:https://ac.nowcoder.com/acm/contest/885/G
    大意: 给你两个由数字组成的字符串(S),(T) 长度为(1e3),问你S中有多少个子序列的值大于字符串T
  2. 思路:开dp[i][j]二维数组,i维护的是t串长度为i的前缀,j维护s串中长度为j的前缀。存储的值是s串的前j缀中大于t串前i缀且长度也为i的子序列的数目。记住,这里维护的是长度也为i的子序列数量。每次状态转移的时候就是要先算上s串中以第j位为结尾的且满足其他条件的子序列数量,再加上之前的(加上之前的就和前缀和一样),其中由于对应位大于小于等于的情况,得记录大于等于和大于两种情况的子序列数量。
  3. 代码有很多细节,看看注释:
#include<bits/stdc++.h>
#define ll long long
#define ms0(x) memset(x,0,sizeof(x))
#define ms-1(x) memset(x,-1,sizeof(x))
using namespace std;
ll mod= 998244353;
const int maxn = 3e3+5;
ll dp1[maxn][maxn]; //大于等于 
ll dp2[maxn][maxn]; //大于
char s[maxn],t[maxn];
int pos0[maxn];
void add(ll &a,ll b) //※※※※这样做应该快一点???  
{
    a+=b;
    if(a>=mod)a-=mod;
}
void sub(ll &a,ll b)
{
    a-=b;
    if(a<0)a+=mod;
}
ll C[maxn][maxn]; 
void predo()    {
    for (int i=C[0][0]=1;i<maxn;++i)
        for (int j=C[i][0]=1;j<=i;++j)
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}//预处理 存储所需的组合数的答案 
int main()
{
    predo();
    int T;
    cin>>T;
    while(T--)
    {
        int m,n;
        for(int j=0;j<=n;j++)
            for(int i=0;i<=m;i++)
                dp1[j][i]=dp2[j][i]=0;   //(+_+)?这里要注意 原来用memset居然会TLE。。。直接用for两层循环清空 
        scanf("%d%d",&m,&n);
        scanf("%s%s",s,t);
        int cnt=0;
        for(int j=0;j<m;j++)			//记录每个0在串s的位置 
        {
            if(s[j]=='0')
            {
                pos0[cnt++]=j+1;
            }
        }
        ll ans=0;
        for(int j=1;j<=m;j++)			//对dp[1]预处理,否者下面的转移方程过程中,dp全是0 
        {
            if(s[j-1]>t[0])
            {
                dp2[1][j]++;
                dp1[1][j]++;      
            } 
            if(s[j-1]==t[0])
            {
                dp1[1][j]++;
            }
            add(dp2[1][j],dp2[1][j-1]);
            add(dp1[1][j],dp1[1][j-1]);
        }
        for(int i=2;i<=n;i++)
        {
            for(int j=min(i,m);j<=m;j++)
            {
                if(s[j-1]>t[i-1])
                {
                    dp2[i][j]=dp1[i-1][j-1];
                    dp1[i][j]=dp1[i-1][j-1];      
                } 
                if(s[j-1]==t[i-1])
                {
                    dp2[i][j]=dp2[i-1][j-1];
                    dp1[i][j]=dp1[i-1][j-1];
                }
                if(s[j-1]<t[i-1])
                {
                    dp2[i][j]=dp2[i-1][j-1];
                    dp1[i][j]=dp2[i-1][j-1];
                }
                add(dp2[i][j],dp2[i][j-1]);
                add(dp1[i][j],dp1[i][j-1]);
            }
        }
        add(ans,dp2[n][m]);
        for(int j=n+1;j<=m;j++)			//最后要加组合数,前面只是加了s串中和t串长度相同的且大于t的子序列的数量 
        {
            add(ans,C[m][j]);
            for(int i=0;i<cnt;i++)
            {
                sub(ans,C[m-pos0[i]][j-1]);	//排除含前导0的子序列 
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
      
}

1.B题 generator 矩阵快速幂 大数幂
2. 题解:并不是把大数转换为二进制再操作,而是按十进制的每一位快速幂,复杂度就变成lg级别的了
3. 代码:

#include<bits/stdc++.h>
#define LL long long
#define ms0(x) memset(x,0,sizeof(x))
#define ms-1(x) memset(x,-1,sizeof(x))
using namespace std;
//-----------------------矩阵快速幂模板-------------------//

const int Matrix_maxn=2;		//矩阵的大小
int mod;
struct Matrix//矩阵用结构体包装
{
    int a[Matrix_maxn][Matrix_maxn];
    void init()//初始化将矩阵变成单位矩阵
    {
        memset(a,0,sizeof(a));
        for(int i=0; i<Matrix_maxn; i++)
            a[i][i]=1;
    }

    void show()
    {
        for(int i=0; i<Matrix_maxn; i++)
        {
            for(int j=0; j<Matrix_maxn; j++)
            {
                printf("%d ",a[i][j]);
            }
            printf("\n");
        }
    }
};
Matrix mul(Matrix  a,Matrix   b)
{
    Matrix ans;
    for(int i=0; i<Matrix_maxn; i++)
    {
        for(int j=0; j<Matrix_maxn; j++)
        {
            ans.a[i][j]=0;
            for(int k=0; k<Matrix_maxn; k++)
            {
                ans.a[i][j]=(ans.a[i][j]+1LL*a.a[i][k]*b.a[k][j])%mod;
            }
        }
    }
    return ans;
}

Matrix qpow(Matrix a, int  n)//矩阵快速幂
{
    Matrix res;
    res.init();
    while(n)
    {
        if(n&1)
            res=mul(res,a);
        a=mul(a,a);
        n>>=1;
    }
    return res;
}

//-----------------------矩阵快速幂模板-------------------//


int main()
{
    ios::sync_with_stdio(false);
    int x0,x1,a,b;
    cin>>x0>>x1>>a>>b;
    string n;
    cin>>n>>mod;
    int len=n.size();
    int tmpnum;
    Matrix base;
    base.a[0][0]=a;
    base.a[1][0]=b;
    base.a[0][1]=1;
    base.a[1][1]=0;
    Matrix ans;
    ans.init();
    Matrix tmp;
    tmp.a[0][0]=x1;tmp.a[0][1]=x0;
    tmp.a[1][0]=0;tmp.a[1][1]=0;
    for(int j=0;j<len;j++)
    {
        tmpnum=n[j]-'0';
        ans=qpow(ans,10);
        ans=mul(ans,qpow(base,tmpnum));
	}
    tmp=mul(tmp,ans);
    cout<<tmp.a[0][1]<<endl;
    return 0;
}

温习矩阵快速幂怎么构造:
https://www.jianshu.com/p/1c3f88f63dec

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值