LibreOJ2044 - 「CQOI2016」手机号码

Posted

V

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LibreOJ2044 - 「CQOI2016」手机号码相关的知识,希望对你有一定的参考价值。

Portal

Description

给出两个十一位数\(L,R\),求\([L,R]\)内所有满足以下两个条件的数的个数。

  • 出现至少\(3\)个相邻的相同数字;
  • 不能同时出现\(4\)\(8\)

Solution

数位DP。
首先将问题转换成\(solve(R)-solve(L)\)的形式,这样只需要求不超过\(n\)的满足条件的数的个数。
定义\(dp[k][x][f_1][f_2][f_3][f_4]\),其中\(k\)表示位数,\(x\)表示尾数,\(f_1\)表示第\(k\)位与第\(k-1\)位是否相同,\(f_2\)表示是否出现过三连,\(f_3\)表示\(4,8\)的出现情况(\(00,01,10,11\)),\(f_4\)表示是否在第\(k\)达到上限。
考虑第\(k+1\)位的每种取值\(i\)。若\(i=x\),则\(f_1=1\);若已有三连或原\(f_1=1\)\(i=x\),则\(f_2=1\);若\(i\)等于\(4\)\(8\),改变\(f_3\);若在第\(k\)位就达到上限且\(i\)等于n的第\(k+1\)位,则\(f_4=1\)
用队列进行转移或循环每一维即可解决。

时间复杂度\(O(11×10×2×2×4×2\cdot10)\)

Code

//「CQOI2016」手机号码
#include <cstdio>
#include <cstring>
#include <queue>
typedef long long lint;
struct state
{
    int k,x,f1,f2,f3,f4;
    state(int _k,int _x,int _f1,int _f2,int _f3,int _f4) {k=_k,x=_x,f1=_f1,f2=_f2,f3=_f3,f4=_f4;}
};
const int LEN=11;
lint dp[12][10][2][2][4][2];
std::queue<state> Q;
int is48(int x) {return (x==8)<<1|(x==4);}
lint solve(lint n)
{
    memset(dp,0,sizeof dp);
    int v[12];
    for(lint i=LEN,t=n;i>=1;i--,t/=10) v[i]=t%10;
    for(int i=1;i<=v[1];i++)
    {
        int f3=is48(i),f4=(i==v[1]);
        dp[1][i][0][0][f3][f4]=1;
        Q.push(state(1,i,0,0,f3,f4));
    }
    while(!Q.empty())
    {
        state s=Q.front(); Q.pop();
        int k=s.k,x=s.x,f1=s.f1,f2=s.f2,f3=s.f3,f4=s.f4,val=dp[k][x][f1][f2][f3][f4];
        if(k==LEN) continue;
        int t=f4?v[k+1]:9;
        for(int i=0;i<=t;i++)
        {
            int _f1=(i==x),_f2=f2||f1&&(i==x),_f3=f3|is48(i),_f4=f4&&i==t;
            lint &r=dp[k+1][i][_f1][_f2][_f3][_f4];
            if(!r) Q.push(state(k+1,i,_f1,_f2,_f3,_f4));
            r+=val;
        }
    }
    lint r=0;
    for(int i=0;i<=9;i++)
        for(int _f1=0;_f1<=1;_f1++)
            for(int _f3=0;_f3<=2;_f3++)
                r+=dp[LEN][i][_f1][1][_f3][0]+dp[LEN][i][_f1][1][_f3][1];
    return r;
}
int main()
{
    lint L,R;
    scanf("%lld%lld",&L,&R);
    if(L==(lint)1e10) printf("%lld\n",solve(R)-solve(L)+1);
    else printf("%lld\n",solve(R)-solve(L-1));
    return 0;
}

以上是关于LibreOJ2044 - 「CQOI2016」手机号码的主要内容,如果未能解决你的问题,请参考以下文章

LibreOJ2095 - 「CQOI2015」选数

LibreOJ2241 - 「CQOI2014」排序机械臂

LibreOJ2097 - 「CQOI2015」任务查询系统

LibreOJ #2095. -「CQOI2015」选数 - 莫比乌斯反演+杜教筛+整除分块

LibreOJ #2016. 「SCOI2016」美味

LibreOJ #2061. 「HAOI2016」放棋子