Uva11361 前缀 分区间

这题用的是书上给的思路,不过,对于这种类型的题,做的太少,于是打算好好谢谢。不过,花了一个晚上,参考别人的代码写了遍。

这种类型的题一般都会用到前缀,设f(n)表示从1到n满足条件的数的个数。但是直接枚举,复杂度为O(n),但是由于范围最大能达到2^31左右,所以不能暴搞。

这里将一个数分成几部分来算:

f[d][m1][m2]表示除出数的第最高位的数的位数是d,各位数的和模k为m1,该数模k为m2的数字的个数。

f[d][m1][m2]=sum{f[d-1][(m1-x)%k][(m2-x*10^d-1)%k]|(0<=x&&x<=9)};

而每个数可以划分成一些f的和。

#include<iostream>
#include<cstdio>
#include<vector>
#include<string>
#include<queue>
#include<cstring>
#define maxn 10005
#define INF 0xfffffff
#define mem(a,b) memset(a,b,sizeof(a))
#define FOR(i,s,t) for(int i=s;i<=t;i++)
#define ull unsigned long long
#define ll long long
using namespace std;
inline int rd(int &x)
{
    char c=getchar();
    while(!isdigit(c))c=getchar();
    x=0;
    while(isdigit(c))
    {
        x=x*10+c-'0';
        c=getchar();
    }
    return x;
}
int buf[10];
inline void ot(int i)
{
    int p=0;
    if(i==0) p++;
    else while(i)
        {
            buf[p++]=i%10;
            i/=10;
        }
    for(int j=p-1; j>=0; j--) putchar('0'+buf[j]);
}
int f[15][100][10005];//数字的长度 各位数和模k 数模k
int count(int x,int k)
{
    if(x<10)//只有一位的时候m1和m2一样的
    {
        return x/k;
    }
    int maxd=0,w=1;//记录最高位后有多少位
    while(x/w>=10)
    {
        maxd++;
        w*=10;
    }
    int ret=0,m1=0,m2=0;//m1,m2分别记录当前位置的前面的数位和及数模k的余数
    for(int i=maxd;i>0;i--)//后面至少有一个10
    {
        int tmp=x/w;
        for(int j=0;j<tmp;j++)
        {
            ret+=f[i-1][((k-j-m1)%k+k)%k][((k-j*w-m2)%k+k)%k];//这里可以通过k直接减
        }
        m1+=tmp;
        m2+=tmp*w;
        x-=tmp*w;
        w/=10;
    }
    //最后一位单独讨论
    for(int i=0;i<=x;i++)
    {
        if(((k-i-m1)%k+k)%k==0&&((k-i-m2)%k+k)%k==0)
        ret++;
    }
    return ret-1;//为啥这里要减1,暂时还没搞懂
}
int main()
{
    int t,a,b,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&a,&b,&k);
        if(k>85)
        {
            printf("0\n");
        }
        else
        {
            memset(f,0,sizeof(f));
            //先求出所有的f[d][m1][m2];
            int size=-1,sx=b;//记录最高位后面有多少个0
            while(sx)
            {
                size++;
                sx/=10;
            }
            for(int i=0; i<=9; i++)
//    最开始的时候只有1位的情况
            f[0][i%k][i%k]+=1;
            int w=1;
            for(int i=1; i<=size; i++)
            {
                w*=10;
                for(int m1=0; m1<k; m1++)
                {
                    for(int m2=0; m2<k; m2++)
                    {
                        for(int x=0; x<10; x++)
                        {
                            f[i][(m1+x)%k][((m2+x*w)%k+k)%k]+=f[i-1][m1][m2];
                        }
                    }
                }
            }
            printf("%d\n",count(b,k)-count(a-1,k));
        }
    }
     return 0;
}

这里还有一个人写得也挺好的:http://blog.csdn.net/lenleaves/article/details/9104417

感觉前面的代码写得不好,我后面又用了两种方法写了下:

个人感觉还是记忆化搜索好理解,这题主要是在初始化的地方出了点错,下次得注意下。

记忆化搜索:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int f[15][100][10005];
int p[11];
void init(int x,int k)
{
    memset(f,-1,sizeof(f));
    int d=0;
    while(x)//计算有多少位
    {
        d++;
        x/=10;
    }
    p[0]=1;
    for(int i=1; i<=d; i++)
    {
        p[i]=(p[i-1]*10)%k;
    }
}
int dp(int i,int m1,int m2,int k)
{
    if(i==0) return (m1==0&&m2==0)?1:0;//这个地方得注意下,就是只有m1,m2
    //都为0的时候才为1,否则其他的请款也得赋值,赋值为0
    else if(f[i][m1][m2]>=0) return f[i][m1][m2];
    int& ret=f[i][m1][m2];
    ret=0;
    for(int x=0;x<10;x++)
    {
        ret+=dp(i-1,((m1-x)%k+k)%k,((m2-x*p[i-1])%k+k)%k,k);
    }
    return ret;
}
int a[11];
int count(int x,int k)
{
    if(!x)//0得特判一下
    {
        return 1;
    }
    int d=0;
    while(x)
    {
        a[d++]=x%10;
        x/=10;
    }
    int m1=0,m2=0,ret=0;
    for(int i=d-1; i>=0; i--)
    {
        if(!i)
            for(int j=0; j<=a[i]; j++)
            {
                ret+=dp(i,((k-j-m1)%k+k)%k,((k-j*p[i]-m2)%k+k)%k,k);
            }
        else
        {
            for(int j=0; j<a[i]; j++)
            {
                  ret+=dp(i,((k-j-m1)%k+k)%k,((k-j*p[i]-m2)%k+k)%k,k);
            }
        }
        m1+=a[i];
        m2+=a[i]*p[i];
    }
    return ret;
}
int main()
{
    int t,a,b,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&a,&b,&k);
        if(k>85)
        {
            printf("0\n");
        }
        else
        {
            init(b,k);
            printf("%d\n",(count(b,k)-count(a-1,k)));
        }
    }
    return 0;
}

递推:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int f[15][100][10005];
int p[11];
void init(int x,int k)
{
    int d=0;
    while(x)//计算有多少位
    {
        d++;
        x/=10;
    }
    p[0]=1;
    for(int i=1; i<d; i++)
    {
        p[i]=(p[i-1]*10)%k;
    }
    memset(f,0,sizeof(f));
    f[0][0][0]=1;
    for(int i=1; i<d; i++)
    {
        for(int m1=0; m1<k; m1++)
        {
            for(int m2=0; m2<k; m2++)
            {
                for(int x=0; x<10; x++)
                {
                    f[i][(m1+x)%k][(m2+x*p[i-1])%k]+=f[i-1][m1][m2];
                }
            }
        }
    }
}
int a[11];
int count(int x,int k)
{
    if(!x)//0得特判一下
    {
        return 1;
    }
    int d=0;
    while(x)
    {
        a[d++]=x%10;
        x/=10;
    }
    int m1=0,m2=0,ret=0;
    for(int i=d-1; i>=0; i--)
    {
        if(!i)
            for(int j=0; j<=a[i]; j++)
            {
                ret+=f[i][((k-j-m1)%k+k)%k][((k-j*p[i]-m2)%k+k)%k];
            }
        else
        {
            for(int j=0; j<a[i]; j++)
            {
                ret+=f[i][((k-j-m1)%k+k)%k][((k-j*p[i]-m2)%k+k)%k];
            }
        }
        m1+=a[i];
        m2+=a[i]*p[i];
    }
    return ret;
}
int main()
{
    int t,a,b,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&a,&b,&k);
        if(k>85)
        {
            printf("0\n");
        }
        else
        {
            init(b,k);
            printf("%d\n",(count(b,k)-count(a-1,k)));
        }
    }
    return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值