数位dp(不要62)&&(ccsu)

不要62

题目链接

题意:输出m到n中不包括62和4的数字的个数;

比如:n=1,m=100,那么62是非法的,41、42、43.....都是非法的。

思路:我们把从0到m的答案算出来,然后把0到n的答案算出来,相减即为最终所求。

那么,如何求出0到m的答案呢?当然借助我们神奇的数位dp。

我们首先要预处理出n的数位(比如100,数位为3)

设置dp方程,dp[pos][sta][limit];  pos:这个数的数位,sta=1表示当前数位的前一个数位是6,limit=0,代表此位没有限制,等于1代表有限制,有限制上界就是a[pos],否则就是9;

比如说       n=2345,pos=2时,即第3个数位,当且仅当前两位是23,第三位才有限制,即i==a[pos]&&limit==1,limit等于1意味着前一个数位等于a[pos],i=a[pos]意味着当前位为a[pos],此时下一位的limit为1。

那么,我们可以dfs枚举每一数个字的合法性;

那么问题来了,会超时吗?这时我们就要引入记忆化搜索,所以大家也不会奇怪为啥明明是dfs枚举,为啥还要一个d数组了吧。

 

 

#include<bits/stdc++.h>
using namespace std;
int n,m;
int d[20][2][2],a[20];

long long dfs(int pos,int sta,int limit)
{
    if(pos==0)return 1;//终点,表示数是合法的
    if(d[pos][sta][limit]) return d[pos][sta][limit];
    int up=(limit==1)?a[pos]:9;
    long long ans=0;
    for(int i=0;i<=up;i++)
    {
        if(i==4||(sta&&i==2))continue;
        ans+=dfs(pos-1,(i==6),limit&&(i==up));
    }
    return d[pos][sta][limit]=ans;
}

long long gao(int n)//求数位
{
    memset(d,0,sizeof(d));
    int cnt=0;
    while(n)
    {
        a[++cnt]=n%10;
        n/=10;
     }
    return dfs(cnt,0,1);
}

int main()
{
    while(~scanf("%d%d",&n,&m)&&n&&m)
    {
        cout<<gao(m)-gao(n-1)<<'\n';
    }
    return 0;
}

ccsu

题目链接

题意:给你一个26进制数n,由a-z表示,要你求出0到n所有的包含ccsu的数的个数;

思路:我们仿照不要62,算出0到n中所有的数,然后减去包含ccsu的数的个数即为答案。

设dp[pos][sta][limit];pos代表字符串的下标,sta等于1时,代表的是这一位的前一位是’c',sta=2代表前面是‘cc',sta=3代表前面是’ccs‘;limit=0,代表此位没有限制,等于1代表有限制,有限制就是a[pos],否则就是’z';

当然也要用记忆话搜索,否则会超时。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 10;
 
char a[maxn];
int d[maxn][5][2],n;
 
long long mod=1e9+7;
int dfs(int pos,int sta,int limit)
{
    if(pos==n+1)return 1;
    if(d[pos][sta][limit])return d[pos][sta][limit];
    char up=(limit==1)?a[pos]:'z';
    long long ans=0;
    for(char i='a';i<=up;i++)
    {
        int tmp=0;
        if(i=='c'&&sta!=1)tmp=1;
        else if(sta==1&&i=='c')tmp=2;
        else if(sta==2&&i=='s')tmp=3;
        else if(sta==3&&i=='u')
        {
            continue;
        }
        ans=(ans+dfs(pos+1,tmp,limit&&i==up))%mod;
    }
    return d[pos][sta][limit]=ans;
}
  
int main()
{
    cin>>a+1;
    n=strlen(a+1);
    long long an=0;
    for(int i=1;i<=n;i++)
    {
        an=(an*26+(a[i]-'a'))%mod;
    }
    an+=1;
    long long tot=an-dfs(1,0,1)+mod;
    cout<<tot%mod<<'\n';
}

对于CCSU这个题,我又发现一种新的解法,我们为啥要去枚举每一位数的非法性,然后用总数去减,我们可以直接枚举每一位数的合法性,具体代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
char a[100004];
int n,d[10004][5][2];
const int mod=1e9+7;
int dfs(int pos,int sta,int limit)
{
    if(pos==n+1)return sta==4?1:0;
    if(d[pos][sta][limit])return d[pos][sta][limit];

    char up=limit?a[pos]:'z';
    long long ans=0;
    for(char i='a';i<=up;i++)
    {
        int tmp = 0;
        if(i == 'c' && sta != 1) tmp = 1;
        else if(i == 'c' && sta == 1) tmp = 2;
        else if(i == 's' && sta == 2) tmp = 3;
        else if(i == 'u' && sta == 3) tmp = 4;
        if(sta==4) tmp = 4;
        ans=(ans+dfs(pos+1,tmp,limit&&i==up))%mod;
    }
    return d[pos][sta][limit]=ans;
}
int main()
{
    cin>>a+1;
    n=strlen(a+1);
    long long an=dfs(1,0,1);
    cout<<an%mod;
}

 

 

最后,特别谢谢ccsu_cat,教会我数位dp,并给我模板,超级感谢

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值