数位DP题目

10 篇文章 0 订阅

Beautiful numbers

解:完美数:一个数能被自己每个数位上的数整除(0不算)。题目问区间[L,R]里有多少完美数。

数位dp,定义dp[i][j][k]为从最高位到i+1位组成的数为j,数位的最小公倍数为k的方案数。每次都记录自己当前组成的数和数位的最小公倍数,组成的数可以取模2520(1乘到10的结果),对答案不影响。

#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <string>
#include <cstring>
#include <set>
#define ll long long
#define inf 0x3f3f3f3f
#define mod 1000000007
using namespace std;
const int MAXN = 51111;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
const int maxn=100010;
typedef long long LL;
ll dp[20][2600][60];
int ma[2600];
int a[30];
int gcd(int x,int y)
{
    return y?gcd(y,x%y):x;
}
int lcm(int x,int y)
{
    if(x==0||y==0) return x+y;
    return x*y/gcd(x,y);
}
ll dfs(int pos,int num,int gbs,bool limit)
{
    if(pos==0) return (num%gbs==0);
    if(!limit&&dp[pos][num][ma[gbs]]!=-1) return dp[pos][num][ma[gbs]];
    ll tmp=0;
    int ed=limit?a[pos]:9;
    for(int i=0;i<=ed;i++)
    {
        tmp+=dfs(pos-1,(num*10+i)%2520,lcm(gbs,i),limit&&i==ed);
    }
    if(!limit) dp[pos][num][ma[gbs]]=tmp;
    return tmp;
}
ll divs(ll x)
{
    int len=0;
    while(x) a[++len]=x%10,x/=10;
    return dfs(len,0,1,1);
}
int main()
{
    ios::sync_with_stdio(false);
    int cnt=0;
    for(int i=1;i<=2520;i++)
    {
        if(2520%i==0) ma[i]=++cnt;
    }
    int T;
    cin>>T;
    while(T--)
    {
        memset(dp,-1,sizeof dp);
        ll l,r;
        cin>>l>>r;
        cout<<divs(r)-divs(l-1)<<'\n';
    }
    return 0;
}

XHXJ's LIS

给定一个区间求区间里的数(数位上的数的最长上升子序列的长度为K的个数)。

对于从高位到低位枚举到的每一个数都用状态压缩来表示他数位上的最长上升子序列(做法是NlogN的求LIS的方法),对于每次枚举到的每一个数位的数都找到前面第一个比他大的数的位置然后插入,直到最后统计是否有K个1。唔~好难解释,还是看代码叭。

#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <string>
#include <cstring>
#include <set>
#define ll long long
#define inf 0x3f3f3f3f
#define mod 1000000007
using namespace std;
const int MAXN = 51111;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
const int maxn=100010;
typedef long long LL;
ll dp[20][(1<<10)+10][11];
int ma[2600];
int a[30];
int get_bit(int x)
{
    int cnt=0;
    while(x)
    {
        if(x&1) cnt++;
        x>>=1;
    }
    return cnt;
}
int get_lis(int x,int k)
{
    for(int i=k;i<=9;i++)
    {
        if(x&(1<<i))
        {
            x^=(1<<i);break;
        }
    }
    x|=(1<<k);
    return x;
}
ll dfs(int pos,int sta,int k,bool limit)
{
    if(pos==0) return (get_bit(sta)==k);
    if(!limit&&dp[pos][sta][k]!=-1) return dp[pos][sta][k];
    ll tmp=0;
    int ed=limit?a[pos]:9;
    for(int i=0;i<=ed;i++)
    {
        if(sta==0&&i==0) tmp+=dfs(pos-1,sta,k,i==ed&&limit);
        else
        {
            int nsta=get_lis(sta,i);
            tmp+=dfs(pos-1,nsta,k,i==ed&&limit);
        }
    }
    if(!limit) dp[pos][sta][k]=tmp;
    return tmp;
}
ll divs(ll x,int k)
{
    int len=0;
    while(x) a[++len]=x%10,x/=10;
    return dfs(len,0,k,1);
}
int main()
{
    ios::sync_with_stdio(false);
    int T;
    memset(dp,-1,sizeof dp);
    int kcas=0;
    cin>>T;
    while(T--)
    {
        ll L,R;int K;
        cin>>L>>R>>K;
        cout<<"Case #"<<++kcas<<": ";
        cout<<(divs(R,K)-divs(L-1,K))<<'\n';
    }
    return 0;
}

Balanced Number

解:找出给定区间内的平衡数(即存在以某一个数位为支点,两边数位到支点的距离乘以数位上的数的和为0)。

dp[pos][pivot][sum] 为在pos位,以第pivot位为支点,当前的和为sum的个数。我们枚举pivot,然后每个都做一次数位dp,再加起来。

//#include <bits/stdc++.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <queue>
#include <map>
#include <string>
#include <cstring>
#include <set>
#define ll long long
#define inf 0x3f3f3f3f
#define mod 998244353
using namespace std;
const int MAXN = 51111;
const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
const int maxn=500010;
typedef long long LL;
int a[40];
ll dp[21][21][2100];
ll dfs(int pos,int pivot,int sum,bool limit)
{
    if(sum<0) return 0;
    if(pos==0) return (sum==0);
    if(!limit&&dp[pos][pivot][sum]!=-1) return dp[pos][pivot][sum];
    ll tmp=0;
    int ed=limit?a[pos]:9;
    for(int i=0;i<=ed;i++)
    {
        tmp+=dfs(pos-1,pivot,sum+(pos-pivot)*i,i==ed&&limit);
    }
    if(!limit) dp[pos][pivot][sum]=tmp;
    return tmp;
}
ll solve(ll x)
{
    int len=0;
    while(x) a[++len]=x%10,x/=10;
    ll ans=0;
    for(int i=1;i<=len;i++) ans+=dfs(len,i,0,1);
    return ans-len+1;//之前每一次的枚举都会把0算一遍,多算了len-1次。
}
int main()
{
    memset(dp,-1,sizeof dp);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        ll x,y;
        scanf("%lld %lld",&x,&y);
        printf("%lld\n",solve(y)-solve(x-1));
    }
    return 0;
}

Harmony Pairs

以前都是做一个数的,这题是两个数的dp。

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define mod 1000000007
#define debug(x) cout<<"test data:"<<(x)<<'\n';
ll dp[110][2010][2][2];//dp[i][j][f1][f2]表示在第i位时A和B的数位的和的差值为j,f1为保证B不超过N的limit,f2为保证B大于A的limit。
char num[110];
int a[110];
ll dfs(int pos,int gap,bool limit1,bool limit2)
{
    //debug(pos)
    if(pos == 0) return (gap<1000)?1ll:0;
    if(dp[pos][gap][limit1][limit2]!=-1) return dp[pos][gap][limit1][limit2];
    int ed = limit1?a[pos]:9;
    ll ans = 0;
    for(int i=0;i<=ed;i++){
        int ed2=limit2?i:9;
        for(int j=0;j<=ed2;j++){
            ans = (ans + dfs(pos-1,gap+(i-j),(limit1&&i==ed),(limit2&ed2==j)))%mod;
        }
    }
    return dp[pos][gap][limit1][limit2]=ans;
}
int main()
{
    memset(dp,-1,sizeof dp);
    scanf("%s",num+1);
    int n=strlen(num+1);
    for(int i=1;i<=n;i++){
        a[n-i+1]=num[i]-'0';
    }
    ll ans = dfs(n,1000,1,1);
    printf("%lld\n",ans);
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值