数位DP day45

数位dp 完结撒花!

明天开始单调队列优化dp 这个还以前做过几道题

数位dp 可以说是一个全新的模型 第一次接触

本来以为会是很套路的模板 不过确实很套路

但最后一题的难度感觉 已经超过了我自己的能力范围

但我还是花了几个小时的时间去搞懂他

想起了xyx说的“瓶颈”

也听说过另一个XYX说的 去做你刚好做又不能做的题 很抽象

不过也不算难看 这道题 

感觉做完不知道这么说

好像是有点成就感 但更多的是担心下次再遇到这种难度的题 我会束手无措

只是停留在做原题 是不正确的 

希望自己能成为xyx说的特别的人 不是前仆后继者

1084. 数字游戏 II

思路一样的

状态表示加一维总和即可

状态计算:f[i][j][k]+=f[i-1][x][k-j];

不过这里要mod 把负的变成正的 

那我们可以自己写一个mod return (x%p+p)%p;

还要一个边界判断 我们这里可以清楚的知道0肯定是一个合法方案 所以return 1即可

但是昨天那个windy数 0也是一个合法方案 为啥return 0呢

那是因为我们后面也相当于整了一棵树 return 就相当于没把 0 纳入考虑的范围

那么我们后面就可以少写一个特判

#include <bits/stdc++.h>
using namespace std;
const int N = 15;
const int M = 1<<12;
//const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int f[N][N][110],l,r,p;//f[i][j][k]表示i位首位为j并且mod p和为k的合法方案数
int mod(int x,int P){
    return (x%P+P)%P;
}
void init(){
    memset(f,0,sizeof f);
    for(int i=0;i<=9;i++)f[1][i][i%p]++;
    for(int i=2;i<N;i++){
        for(int j=0;j<=9;j++){
            for(int k=0;k<p;k++){
                for(int x=0;x<=9;x++){
                    f[i][j][k]+=f[i-1][x][mod(k-j,p)];
                }
            }
        }
    }
}
int dp(int n){
    if(!n)return 1;
    vector<int>v;
    while(n)v.push_back(n%10),n/=10;
    int res=0,last=0;
    for(int i=v.size()-1;i>=0;i--){
        int x=v[i];
        for(int j=0;j<x;j++){
            res+=f[i+1][j][mod(-last,p)];
        }
        last+=x;
        if(!i&&last%p==0)res++;
    }
    return res;
}
signed main(){
    fast
    while(cin>>l>>r>>p){
        init();
        cout<<dp(r)-dp(l-1)<<endl;
    }
    return ~~(0^_^0);
}

1085. 不要62

这个还挺简单的

最开始一看到这个就想到了状态机+KMP 可是一看居然只有两个字符串 而且都才一两位

那我们就可以直接做了

注意一点就是我们枚举j的时候有可能此位是345679 j自然就有2这种情况 而如果last是6的话自然要skip掉 因为我们加的时候只是n位上首位为2的 没有包括前面6这个限制条件 所以还是有值的

细节很多啊! 

不光是后面的break 看来还需磨练啊!

#include <bits/stdc++.h>
using namespace std;
const int N = 15;
const int M = 1<<12;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int f[N][N],l,r;
void init(){
    for(int i=0;i<=9;i++)f[1][i]=1;
    f[1][4]=0;
    for(int i=2;i<N;i++){
        for(int j=0;j<=9;j++){
            if(j==4)continue;
            else if(j==6){
                for(int k=0;k<=9;k++){
                    if(k!=2)f[i][j]+=f[i-1][k];
                }
            }else{
                for(int k=0;k<=9;k++){
                    f[i][j]+=f[i-1][k];
                }
            }
        }
    }
}
int dp(int n){
    if(!n)return 1;
    vector<int>v;
    while(n)v.push_back(n%10),n/=10;
    int res=0,last=0;
    for(int i=v.size()-1;i>=0;i--){
        int x=v[i];
        for(int j=0;j<x;j++){
            if(last == 6 && j == 2)continue;
            res+=f[i+1][j];
        }
        if(x==4||(x==2&&last==6))break;
        last=x;
        if(!i)res++;
    }
    return res;
}
signed main(){
    fast
    init();
    while(cin>>l>>r,l&&r){
        cout<<dp(r)-dp(l-1)<<endl;
    }
    return ~~(0^_^0);
}

1086. 恨7不成妻

哈哈 全局变量的结构体居然还要引用才可以修改

2:任意一个数与last_a或者last_b关于p互补都不是合法的

3:算res时要把前面的位数算进去那么就相当于j换成了last_a * power9[i+1]

4:我们最后树的右边应该是他自己的平方和

回到这道题本身

我们可以轻松的推出状态表示 前i位 首位为j 每位sum为k 总sum为m的平方和

最开始我以为状态转移就是加上每一位的平方和就可以了

看来还是我太傻逼了(就像是10001^2 == 10000^2 +1^2)

肯定是错误的!

我们把当前为jXXXXX.... 我们抽象把后面的i-1位 成一个数 A

合法的解的平方和=jA1^2+jA2^2+jA3^2+....+jAt^2              (jA=j*10^(i-1)+A)

=t(j*10^(i-1))^2+2*(j*10^(i-1))*(A1+A2+A3+....+At)+(A1^2+A2^2+...)

如果我们只维护平方和 那我们的下一个平方和是递推不出来的

看上式 我们还需要维护的有t 还有(A1+A2+A3+....+At) 这个暂且称为数的和

好 接下来那我们如何 维护这些信息呢

我们先把状态转移方程写下来:

f[i][j][k][m]<-f[i-1][x][k-j][m-j*10^i-1]

         v1                        v2

我们分别把要维护的三个信息 写成结构体的形式  //s0 cnt s1 sum s2 sqrt sum

合法的数量 自然只用 v1.s0+=v2.s0 即可 这里是可以%p的 这里我一开始想的是不能%

但其实你的cnt都要拿进去算 并且又没有除法 当然可以直接%掉

v1.s1 即是i位j首位的数的总和 我们知道v2的数量 并且v2的前面接的就是v1的j

v1.s1+=v2.s0*j*10^i-1+v2.s1;

v1.s2就要用最开始的那个方程来算了t(j*10^(i-1))^2+2*(j*10^(i-1))*(A1+A2+A3+....+At)+(A1^2+A2^2+...)

预处理结束

正式开始我们的树

我们用last1存前面位的前缀 last2存前面位sum

当然我们进去时要把a=-last1*10^i%7 b=-last2%7

算出我们不可以凑到的a b 其实就是一个互补的关系 这里取负号 最开始我没看到符号我一直以为他写反了 哈哈

然后我们要把满足该位满足要求的所有数取出来 

这里取出来就是同一位上面的 直接+上就可以了

让后我们就相当于有个前缀last1而不是最开始j了

最后要是出现 7 break 掉 否则更新last1 last2

注意这里last1一直让他从小的位数开始 其实也可以直接i位开始 感觉会更好写一点

最后有幸走到右边树 并且都是合法的 那么我们加上原本数的平方和即可

完结撒花!

#include <bits/stdc++.h>
using namespace std;
const int N = 20;
const int M = 1<<12;
//const int mod = 1e9+7;
#define int long long
#define LL long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
struct F{
    int s0,s1,s2;  //s0 cnt s1 sum s2 sqrt sum
}f[N][10][7][7];
int p=1e9+7,power7[N],power[N];
int mod(int x,int y){
    return (x%y+y)%y;
}
void init(){
    power7[0]=power[0]=1;
    for(int i=1;i<N;i++){
        power7[i]=power7[i-1]*10%7;
        power[i]=power[i-1]*10%p;
    }
    for(int i=0;i<=9;i++){
        if(i==7)continue;
        auto &v=f[1][i][i%7][i%7];
        v.s0++;
        v.s1+=i;
        v.s2+=i*i;
    }
    int power1=10;
    for(int i=2;i<N;i++,power1*=10){
        for(int j=0;j<=9;j++){
            if(j==7)continue;
            for(int k=0;k<7;k++){
                for(int m=0;m<7;m++){
                    for(int x=0;x<=9;x++){
                        if(x==7)continue;
                        auto &v1=f[i][j][k][m],&v2=f[i-1][x][mod(k-j*mod(power1,7),7)][mod(m-j,7)];
                        v1.s0=(v1.s0+v2.s0)%p;
                        v1.s1=(v1.s1+v2.s0*j%p*(power1%p)+v2.s1)%p;
                        v1.s2=(v1.s2+j*j%p*(power1%p)%p*(power1%p)%p*v2.s0%p+2*j*(power1%p)%p*v2.s1%p+v2.s2)%p;
                    }
                }
            }
        }
    }
}
F get(int i,int j,int k,int m){
    int s0=0,s1=0,s2=0;
    for(int x=0;x<7;x++){
        for(int y=0;y<7;y++){
            if(k==x||y==m)continue;
            else{
                auto v=f[i][j][x][y];
                s0=(s0+v.s0)%p;
                s1=(s1+v.s1)%p;
                s2=(s2+v.s2)%p;
            }
        }
    }
    return {s0,s1,s2};
}
int dp(int n){
    if(!n)return 0;
    int cnt=n%p;
    vector<int>v;
    while(n)v.push_back(n%10),n/=10;
    int res=0,last_a=0,last_b=0;
    for(int i=v.size()-1;i>=0;i--){
        int x=v[i];
        int a=mod(-last_a*power7[i+1],7);
        int b=mod(-last_b,7);
        for(int j=0;j<x;j++){
            if(j==7)continue;
            auto v1=get(i+1,j,a,b);
            res=(res +
                 (last_a % p) * (last_a % p) % p * (power[i + 1] % p) % p * (power[i + 1] % p) % p * v1.s0 % p +
                 2 * (last_a % p) * (power[i + 1] % p) % p * v1.s1 % p +
                 v1.s2)%p;
        }
        if(x==7)break;
        last_a*=10,last_a+=x;
        last_b+=x;
        if(!i&&last_a%7&&last_b%7)res=(res+cnt*cnt)%p;
    }
    return res;
}
signed main(){
    fast
    init();
    int t;cin>>t;
    while(t--){
        int l,r;cin>>l>>r;
        cout<<mod(dp(r)-dp(l-1),p)<<endl;
    }
    return ~~(0^_^0);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值