ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

数位DP

2020-12-11 20:03:40  阅读:164  来源: 互联网

标签:return val int LL pos mt DP 数位


题目一 含007的子序列的计数

好朋友(https://ac.nowcoder.com/acm/problem/19327)

题目描述

BLUESKY007有很多关系很好的朋友,他们无一例外,名字均由数字组成(首字符不为0)且含有"007"(例如“10007”,“10707”就是她的好朋友,而“97037”,“70709”不是),即对于字符串s存在i,j,k(i< j< k)满足
s[i]s[j]s[k]= 007.虽然BLUESKY007眼力极佳,一眼就能看出一个人是不是自己的好朋友,但BLUESKY007是个蒟蒻,她并不擅长数数,但她又想知道在[li,ri]内有多少人是自己的好朋友,所以就找到了你来帮忙.
她会向你询问t次,由于询问次数可能很多,所以你只需要告诉她t次询问答案的异或和即可.

输入描述:

第一行一个整数t,表示询问个数
接下来t行,每行两个整数li,ri

输出描述:

一行一个整数表示答案

示例1

输入
3
1 1000
233 666
999 999
输出
0
在0~1000范围内不存在与题目要求相符的含有“007”的数,所以三次询问的答案都是0

示例2

输入
3
10000 10086
2333 23333
6666 66666
输出
132

备注:

对于20%的数据,li≤ri≤103
对于40%的数据,t≤50,li≤ri≤5·10^4

思路

\(f[i][j][k]\):前i位,状态j,是否有前导0的的方案数。
j=0 没有0
j=1前面包含0
j=2前面包含00
j=3前面包含007

#include <bits/stdc++.h>
#define LL long long
using namespace std;

int a[25];
//pos: 位数 ok:007 Lead: 前导0 mt:满足限制
/*
ok=0 没有0
ok=1前面包含0
ok=2前面包含00
ok=3前面包含007
*/
LL f[20][5][2];
LL dfs(int pos, int ok, bool Lead, bool mt){
    if(pos==-1){
        if(ok==3) return 1;
        else return 0;
    }
    if(!mt&&f[pos][ok][Lead]!=-1) return f[pos][ok][Lead];
    int Len=mt?a[pos]:9;
    LL ans=0;
    for(int i=0; i<=Len; i++){
        if(i==0){
            if(Lead){//有前导0
                ans+=dfs(pos-1, ok, Lead, mt&&i==a[pos]);
            }
            else{//没有前导0
                if(ok==0) ans+=dfs(pos-1, 1, Lead, mt&&i==a[pos]);
                else if(ok==1) ans+=dfs(pos-1, 2, Lead, mt&&i==a[pos]);
                else if(ok==2) ans+=dfs(pos-1, 2, Lead, mt&&i==a[pos]);
                else if(ok==3) ans+=dfs(pos-1, 3, Lead, mt&&i==a[pos]);

            }
        }
        else if(i==7){
            if(ok==2) ans+=dfs(pos-1, 3, 0, mt&&i==a[pos]);
            else ans+=dfs(pos-1, ok, 0, mt&&i==a[pos]);
        }
        else ans+=dfs(pos-1, ok, 0, mt&&i==a[pos]);
    }
    return (!mt)?f[pos][ok][Lead]=ans:ans;

}

LL slove(LL x){
    int pos=0;
    while(x){
        a[pos++]=x%10;
        x/=10;
    }

    return dfs(pos-1, 0, 1, 1);
}

int main() {

    memset(f, -1, sizeof(f));
    int t; scanf("%d", &t);
    LL res=0;
    while(t--){
        LL L, R; scanf("%lld%lld",&L, &R);
        res^=(slove(R)-slove(L-1));
    }
    printf("%lld\n", res);

    return 0;
}

题目二 出现连续3个相同的数字

[CQOI2016]手机号码(https://ac.nowcoder.com/acm/problem/19945)

题目描述

人们选择手机号码时都希望号码好记、吉利。比如号码中含有几位相邻的相同数字、不含谐音不吉利的数字等。手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号 码单独出售。为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数 量。
工具需要检测的号码特征有两个:号码中要出现至少3个相邻的相同数字,号码中不能同时出现8和4。号码必须同时包含两个特征才满足条件。满足条件的号码例如:13000988721、 23333333333、14444101000。而不满足条件的号码例如:1015400080、10010012022。 手机号码一定是11位数,前不含前导的0。工具接收两个数L和R,自动统计出[L,R]区间 内所有满足条件的号码数量。L和R也是11位的手机号码。

输入描述:

输入文件内容只有一行,为空格分隔的2个正整数L,R。
10^10 ≤ L ≤ R < 10^11

输出描述:

输出文件内容只有一行,为1个整数,表示满足条件的手机号数量。

示例1

输入
12121284000 12121285550
输出
5

说明

样例解释
满足条件的号码: 12121285000、 12121285111、 12121285222、 12121285333、 12121285550

思路

我们需要状态记录上上位last1, 上位last,是否满足有3个连续的数字。是否有8,是否有4就可以了。

#include <bits/stdc++.h>
#define LL long long
using namespace std;

int a[25];

LL f[20][11][11][2][2][2][2];
//pos: 位数 last1:上上位 last1:上位 ok:出现3连 cx8:出现8 cx4:出现4 mt:限制
LL dfs(int pos, int last1, int last2, bool ok, bool cx8, bool cx4, bool mt){
    if(pos==-1){
        return ok;
    }
    if(f[pos][last1][last2][ok][cx8][cx4][mt]!=-1) return f[pos][last1][last2][ok][cx8][cx4][mt];
    int Len=mt?a[pos]:9;
    LL ans=0;
    for(int i=0; i<=Len; i++){
        if(cx4&&i==8||cx8&&i==4) continue;
        if(last2==10&&i==0){
            ans+=dfs(pos-1, last1, last2, ok||(i==last1&&i==last2), cx8||(i==8), cx4||(i==4), mt&&i==a[pos]);
        }
        else{
            ans+=dfs(pos-1, last2, i, ok||(i==last1&&i==last2), cx8||(i==8), cx4||(i==4), mt&&i==a[pos]);
        }

    }
    return f[pos][last1][last2][ok][cx8][cx4][mt]=ans;
}

LL slove(LL x){
    int pos=0;
    while(x){
        a[pos++]=x%10;
        x/=10;
    }
    memset(f, -1, sizeof(f));
    return dfs(pos-1, 10, 10, 0, 0, 0, 1);
}

int main() {

    LL L, R; scanf("%lld%lld",&L, &R);
    LL res=(slove(R)-slove(L-1));
    printf("%lld\n", res);

    return 0;
}

题目三 数位和能够整除原数计数

[AHOI2009]SELF 同类分布(https://ac.nowcoder.com/acm/problem/19888)

题目描述

给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数。1<=a, b<=1e18

输入描述:

输入a,b

输出描述:

输出[a,b]中各位数字之和能整除原数的数的个数

示例1

输入
10 19
输出
3

思路

有变化的量。我们不可能记录原数,和数位和。原数的状态有1e18个。数位和最大9*17。
如果我们能够记录(原数%数位和)就好了。可是数位和我们不知道。我们这个地方枚举数位和就可以了。

#include <bits/stdc++.h>
#define LL long long
using namespace std;

int a[25];
//pos: 位数 sum:数位和 数%mod mt:满足限制
LL f[20][200][200], mod;
LL dfs(int pos, int sum, int x, bool mt){
    if(sum>mod) return 0;
    if(pos==-1){
        if(x==0&&sum==mod) return 1;
        else return 0;
    }
    if(!mt&&f[pos][sum][x]!=-1) return f[pos][sum][x];
    int Len=mt?a[pos]:9;
    LL ans=0;
    for(int i=0; i<=Len; i++){
        ans+=dfs(pos-1, sum+i, (x*10+i)%mod, mt&&i==a[pos]);
    }
    return (!mt)?f[pos][sum][x]=ans:ans;
}

LL slove(LL x){
    int pos=0;
    while(x){
        a[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1, 0, 0, 1);
}

int main() {

    LL a, b; scanf("%lld%lld", &a, &b);
    LL ans=0;
    for(int i=1; i<=9*19; i++){
        mod=i;
        memset(f, -1, sizeof(f));
        ans+=slove(b)-slove(a-1);
    }
    printf("%lld\n", ans);

    return 0;
}

题目四 1~9的出现次数

[ZJOI2010]COUNT 数字计数 (https://ac.nowcoder.com/acm/problem/20491)

题目描述

给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。

输入描述:

输入文件中仅包含一行两个整数a、b,含义如上所述。

输出描述:

输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。

示例1

输入
1 99
输出
9 20 20 20 20 20 20 20 20 20

思路

枚举每个数码就可以了。

#include <bits/stdc++.h>
#define LL long long
using namespace std;

int a[25];
LL f[25][25][2][10];
LL dfs(int pos, int x, bool Lead, bool mt, const int key){
    if(pos==-1){
        return x;
    }
    if(!mt&&f[pos][x][Lead][key]!=-1) return f[pos][x][Lead][key];
    int Len=mt?a[pos]:9;
    LL ans=0;
    for(int i=0; i<=Len; i++){
        if(Lead&&i==0) ans+=dfs(pos-1, x, Lead, mt&&(i==a[pos]), key);
        else ans+=dfs(pos-1, x+(i==key), Lead&&(i==0), mt&&(i==a[pos]), key);
    }
    return mt?ans:f[pos][x][Lead][key]=ans;
}

LL slove(LL x, int key){
    int pos=0;
    while(x){
        a[pos++]=x%10;
        x/=10;
    }
    //cout<<dfs(pos-1, 0, 1, 1, key)<<endl;
    return dfs(pos-1, 0, 1, 1, key);
}

int main() {

    memset(f, -1, sizeof(f));
    LL a, b; scanf("%lld%lld", &a, &b);
    for(int i=0; i<=9; i++){
        printf("%lld%c", slove(b, i)-slove(a-1, i), (i==9?'\n':' '));
    }

    return 0;
}

题目五 f[i]数位和=i

[SDOI2013]淘金 (https://ac.nowcoder.com/acm/problem/20359)

题目描述

小Z在玩一个叫做《淘金者》的游戏。游戏的世界是一个二维坐标。X轴、Y轴坐标范围均为1..N。初始的时候,所有的整数坐标点上均有一块金子,共N*N块。(1=<N<=1e12)
一阵风吹过,金子的位置发生了一些变化。细心的小Z发现,初始在(i,j)坐标处的金子会变到(f(i),f(j))坐标处。其中f(x)表示x各位数字的乘积,例如f(99)=81,f(12)=2,f(10)=0。如果金子变化后的坐标不在1..N的范围内,我们认为这块金子已经被移出游戏。同时可以发现,对于变化之后的游戏局面,某些坐标上的金子数量可能不止一块,而另外一些坐标上可能已经没有金子。这次变化之后,游戏将不会再对金子的位置和数量进行改变,玩家可以开始进行采集工作。
小Z很懒,打算只进行K次采集。每次采集可以得到某一个坐标上的所有金子,采集之后,该坐标上的金子数变为0。
现在小Z希望知道,对于变化之后的游戏局面,在采集次数为K的前提下,最多可以采集到多少块金子?
答案可能很大,小Z希望得到对1000000007(10^9+7)取模之后的答案。

输入描述:

共一行,包含两介正整数N,K。

输出描述:

一个整数,表示最多可以采集到的金子数量。

示例1

输入
1 2 5
输出
18

思路

\(我们先考虑1~1e12的数有多少个不同的数位和v[](实测有8000多个)。再考虑对于每个v[]中存在的数x有多少个数c[x]可以变成它。\)
\(那么坐标(i, j)的矿数量:c[i]*c[j]\)
\(那么我们要求两个c[]的乘积的前k大的和。有经典的O(k*logn)做法\)

  1. 先考虑1~1e12的数有多少个不同的数位和v[] 这里用数位DP减少暴搜的复杂度。
unordered_map<LL, bool> mp, vis[15];
vector<int> v;
//前pos位 乘积为val的状态
void DFS(int pos, LL val, bool mt){
    if(pos==-1){
        if(mp.count(val)) return ;
        mp[val]=true; v.push_back(val);
        return ;
    }
    if(vis[pos][val]) return ;
    vis[pos][val]=1;
    int Len=mt?a[pos]:9;
    for(int i=1; i<=Len; i++){
        DFS(pos-1, val*i, 0);
    }
}
void init(LL x){
    while(x){
        a[pos++]=x%10; x/=10;
    }
    //因为没有0坐标,所以数位不能存在0,必须枚举位数
    for(int i=pos; i>=1; i--) DFS(i-1, 1, i==pos);
}

现在得到v[i]数组。我们要求在1~n有多少个数位和可以变成v[i]。
这里是常规的数位DP。
f[pos][val]:前pos位乘积为val的个。枚举每一个位i。
if(val%i) f[pos][val]+=f[pos-1][val/i]

unordered_map<LL, LL> f[15];
LL dfs(int pos, LL val, bool mt){
    if(pos==-1){
        return val==1;
    }
    if(!mt&&f[pos].count(val)) return f[pos][val];
    int Len=mt?a[pos]:9;
    LL ans=0;
    for(int i=1; i<=Len; i++){
        if(val%i==0) ans+=dfs(pos-1, val/i, mt&&i==a[pos]);
    }
    return (!mt)?f[pos][val]=ans:ans;
}
 
LL slove(LL x){
    LL ans=0;
    for(int i=pos; i>=1; i--){
        ans+=dfs(i-1, x, i==pos);
    }
    return ans;
}

最后就是一个求c[]数组和自己的乘积的前k大和。

#include <bits/stdc++.h>
#define LL long long
using namespace std;
 
int a[15], pos=0;
unordered_map<LL, bool> mp, vis[15];
vector<int> v;
void DFS(int pos, LL val, bool mt){
    if(pos==-1){
        if(mp.count(val)) return ;
        mp[val]=true; v.push_back(val);
        return ;
    }
    if(vis[pos][val]) return ;
    vis[pos][val]=1;
    int Len=mt?a[pos]:9;
    for(int i=1; i<=Len; i++){
        DFS(pos-1, val*i, 0);
    }
}
 
void init(LL x){
    while(x){
        a[pos++]=x%10; x/=10;
    }
    for(int i=pos; i>=1; i--) DFS(i-1, 1, i==pos);
}
 
unordered_map<LL, LL> f[15];
LL dfs(int pos, LL val, bool mt){
    if(pos==-1){
        return val==1;
    }
    if(!mt&&f[pos].count(val)) return f[pos][val];
    int Len=mt?a[pos]:9;
    LL ans=0;
    for(int i=1; i<=Len; i++){
        if(val%i==0) ans+=dfs(pos-1, val/i, mt&&i==a[pos]);
    }
    return (!mt)?f[pos][val]=ans:ans;
}
 
LL slove(LL x){
    LL ans=0;
    for(int i=pos; i>=1; i--){
        ans+=dfs(i-1, x, i==pos);
    }
    return ans;
}
 
int now[10005];
LL c[10005];
struct data{
    int x,y;
    bool operator < (data w) const{ return (LL)c[x]*c[y] < (LL)c[w.x]*c[w.y]; }
}t;
priority_queue<data>H;
LL sum_Kth(LL n, int k, const LL P){
    sort(c+1,c+n+1),reverse(c+1,c+n+1);
    for (int i = 1; i <= n; ++i) now[i] = 1,t.x = i,t.y = 1,H.push(t);
    LL ans=0;
    while (k--){
        t = H.top();
        ans = (ans + c[t.x] % P * (c[t.y] % P)) % P;
        //cout<<c[t.x]*c[t.y]<<endl;
        H.pop();
        ++now[t.x];
        if (now[t.x] <= n) ++t.y,H.push(t);
    }
    return ans;
}
 
 
int main() {
 
    LL n, k; scanf("%lld%lld", &n, &k);
    init(n);//得到所有的f[i]可能取值
    int cut=0;
    for(auto x: v){
        c[++cut]=slove(x);
        //cout<<x<<" "<<c[cut]<<endl;
    }
    printf("%lld\n", sum_Kth(cut, k, 1e9+7));
 
    return 0;
}

总结

1.要数组重用,必须不能有mt,这个是和输入有关系的
2.多个状态不确定时,枚举状态数小的状态。

标签:return,val,int,LL,pos,mt,DP,数位
来源: https://www.cnblogs.com/liweihang/p/14122435.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有