数位DP

https://oi-wiki.org/dp/number/


技巧类:(1)如果多组数据,dp的一个维度“是否贴上界”可以省略,直接在只不贴上界的时候去算合法方案数,这样不同的数计算就是可以继承的,不用再memset一遍

(2)前导0:

 

就是好比说统计234560--12113124324中0、1、2出现的次数总计

 

如果1111110__那么0这一位的0贡献可以计入

 

如果000000__这就不能计入,注意一下需不需要额外记录就行

(3)放缩思想:对于题目的某些条件进行分析,可以化简成某种更容易表示的状态。当我们找到这样一种共性的表示,就可以知道对于不同的数具有相同贡献,我们计数或者选择最优,就有头绪了

 

###windy数

思路就是     要求相邻位置数字之差<=2的数的个数,不包含前导0,我分解成每一位累计计算贡献,虽然依旧是暴力枚举但是记忆化会去掉很多重复的低位枚举。



int A,B;
int num[14],len,dp[14][14][2][2];
inline int dfs(int now,int last,int top,int _0_)
{
	//chu("find:%d %d %d %d\n",now,last,top,_0_);
	if(dp[now][last][top][_0_])return dp[now][last][top][_0_];
	if(!now)return 1;
	int res=0;
	//枚举当前位填什么
	_f(i,0,9)
	{
		if((i<=num[now]||(!top))&&(abs(i-last)>=2||_0_))
		res+=dfs(now-1,i,top&&(num[now]==i),_0_&&(!i));
	}
	//chu("dp[%d][%d][%d][%d]:%d\n",now,last,top,_0_,res);
	return dp[now][last][top][_0_]=res;
}
inline int solve(int x)
{
	if(!x)return 1;
	len=0;
	memset(dp,0,sizeof(dp));
	memset(num,0,sizeof(num));
	while(x){num[++len]=x%10;x/=10;}
	//chu("%d %d\n",num[1],num[2]);
	return dfs(len,11,1,1);
}
int main()
{
	//freopen("","r",stdin);
	//freopen("","w",stdout);
	A=re(),B=re();
	chu("%d",solve(B)-solve(A-1));
	return 0;
}
 
 

###花神数论:给你一个正整数N,求[1,N]内的数,每一个数的二进制位1的个数的乘积。 (n<=1e15)

考虑1的个数为k的数有几个。我们设dp[i][j][k][op]:表示当前累积到第i位,已经有j个1,目标是k个1,是否是上界的数的个数。每次清空dp找1的个数是m的数有几个。

(1)优化在于可能对于某一位而言,前面有各种方案组成j个1,但是对于后面等效,所以算出后面满足要求的含有1的合法个数,下次递归就不用再找了

(2)因为是统计合法数个数,所以可能是dp=0,但是记忆化有效,必须初始化-1

ll n;
int num[55],cnt;ll dp[55][55][55][2];
ll ans[55];
//当前多少位,当前已经有多少1,目标要多少1,是否上界
inline ll dfs(int now,int sum,int goal,int up)
{
    if(!now)return (goal==sum);
    if(dp[now][sum][goal][up]!=-1)return dp[now][sum][goal][up];
    int lim=((up&&num[now]==0)?0:1);ll res=0;
    _f(i,0,lim)
    if(sum+i<=goal)res+=dfs(now-1,sum+i,goal,(up&&(num[now]==i)));
    return dp[now][sum][goal][up]=res;
}
inline ll qpow(ll a,ll b)
{
    ll bas=1;
    while(b){if(b&1)bas=(ll)bas*a%mod;b>>=1;a=(ll)a*a%mod;}
    return bas;
}
inline ll solve(ll x)
{
    while(x)
    {
        num[++cnt]=((x&1)>0);x>>=1;//从低到高
    }
    //_f(i,1,cnt)chu("num:%d\n",num[i]);
//    chu("cnt:%d\n",cnt);
    ll anse=1;
    _f(i,1,50)
    {
        memset(dp,-1,sizeof(dp));
        ans[i]=dfs(cnt,0,i,1);
    //    chu("ans[%d]:%lld\n",i,ans[i]);
    }
    _f(i,1,50)
    {
        anse=anse*qpow(i,ans[i])%mod;
    }
    return anse;
}
int main()
{
    n=re();
    chu("%lld",solve(n));
    return 0;
}
AC

 

###手机号码

存在一种数字重复出现>=3次,4和8不能重复出现。[L,R]区间内满足要求的数个数。

8维状态随便设,只要记忆化并且dp表示的状态只和当前位、上一位有关,就可以跑得飞快而且正确。

const int N=5e5+100;const ll mod= 10000007;
ll dp[17][11][17][2][2][2][2][2];
int num[17],shu;
inline ll dfs(int now,int last,int cot,int p,int q,int k,int up,int fir)
{
    //if(!now)chu("k:%d  p:%d  q:%d\n",k,p,q);
    if(p&&q)return 0;
    if(!now)return k&&(!(p&q));//必须要连续(p和q)而且4,8不重复出现
    if(dp[now][last][cot][p][q][k][up][fir]!=-1)return dp[now][last][cot][p][q][k][up][fir];
    int lim=(up?num[now]:9);ll res=0;
    for(rint i=0;i<=lim;++i)
    {
        if(fir&&(!i))continue;
        int lian=((last==i)?(cot+1):1);
        res+=dfs(now-1,i,lian,i==4||p,i==8||q,k|(lian>=3),(up&&num[now]==i),0);
    }
    return dp[now][last][cot][p][q][k][up][fir]=res;
}
ll L,R;
int jishu,yici,_4,_8;
inline ll solve(ll x)
{
    memset(dp,-1,sizeof(dp));
    memset(num,0,sizeof(num));
    shu=0;
    while(x)
    {
        num[++shu]=x%10;x/=10;
    }
     jishu=1,yici=0,_4=0,_8=0;
    _f(i,1,shu)
    {
        if(num[i]==4)_4=1;
        if(num[i]==8)_8=1;
        if(i<=shu-2&&num[i]==num[i+1]&&num[i+1]==num[i+2])yici=1;
    }
    if(!yici)jishu=0;
    if(_4&&_8)jishu=0;
    //chu("_4:%d  _8:%d\n",_4,_8);
    //f_(i,shu,1)chu("%d",num[i]);
    return dfs(shu,11,1,0,0,0,1,1);//-((x==L)?jishu:0);
}
int main()
{
     L=re(),R=re();
    //chu("solve:%lld\n",solve(L-1));
    //return 0;
    chu("%lld",solve(R)-solve(L)+jishu);
    //chu("jishu:%d\n",jishu);
    return 0;
}
AC

 ###haha数

让你求[L,R]区间内满足这个数字的每一位可以整除这个数的数的个数

一开始想我只有在搜完了所有数之后才知道这个数能不能被每一位整除,但是每一个数又都是唯一的,所以就没思路了。但是我们可以放缩(1)1e18的数据范围很大,但是对于x这个数%2520之后的才有意义(2520是1---9的公倍数),所以只保留这个信息就可以归纳许多状态。(2)我们不需要确定了数之后挨个比较每一位整除,只要保留他们的lcm就行。最后判断sum%lcm==0就可以

const int N=1e5+10;
int num[20],cnt;
ll dp[20][2550][50];
int fnd[2550],shu;
inline int get_gcd(int x,int y)
{
    if(!y)return x;
    return get_gcd(y,x%y);
}
inline int get_lcm(int x,int y)
{
    //chu("lcm:%lld %lld\n",x,y);
    return x*y/get_gcd(x,y);
}
inline ll dfs(int now,int sum,int lcm,int up)
{
    //chu("%d %d %d %d\n",now,sum,lcm,up);
    if(!now)return lcm&&(sum%lcm==0);//默认0不算
    if(up==0&&dp[now][sum][fnd[lcm]]!=-1)return dp[now][sum][fnd[lcm]];
    int lim=(up)?num[now]:9;ll res=0;
    _f(i,0,lim)
    {
        int Lcm;
        if(i==0)Lcm=lcm;
        else if(lcm==0)Lcm=i;
        else Lcm=get_lcm(lcm,i);
        res+=dfs(now-1,(sum*10+i)%2520,Lcm,up&&num[now]==i);
    }
    if(!up)
    return dp[now][sum][fnd[lcm]]=res;
    else return res;
}
inline ll solve(ll x)
{
    cnt=0;
    while(x)
    {
        num[++cnt]=x%10;x/=10;
    }
    //chu("dfsf\n");
    return dfs(cnt,0,0,1);
}
int main()
{
//    freopen("1.in","r",stdin);
//    freopen("a.txt","w",stdout);
    int T=re();
    // int bj=sqrt(2520);
    // _f(i,2,bj)
    // {
    //     if(!(2520%i))fnd[i]=++shu,fnd[2520/i]=++shu;
    // }
    memset(dp,-1,sizeof(dp));
    _f(i,1,2520)
    if(!(2520%i))fnd[i]=++shu;
    //chu("df\n");
    while(T--)
    {
        ll l=re(),r=re();
        //chu("%lld\n",solve(l-1));
        chu("%lld\n",solve(r)-solve(l-1));
    }

    return 0;
}
AC

 ###苍与红的试炼

给你是s,d,求能整除d,每位上和是s的数的最小(没有就输出-1)(s<=5000,d<=500)

dfs对于无穷状态肯定暴栈,因为不知道边界,但是bfs就可以,因为每次选择最优(位数:入队出队顺序;值:从0~9的循环),只要有结果就一定是最优的。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<deque>
#include<queue>
#include<map>
#include<bitset>
#include<set>
#include<ctime>
using namespace std;
#define _f(i,a,b)  for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define rint register int
#define ll long long
#define chu printf
#define INF 2147483647
inline ll re()
{
    ll x=0,h=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')h=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*h;
}
const int N=1e5+10;
struct Number
{
    int mod,sum;
    Number(){}
    Number(int a,int b){mod=a,sum=b;}
};
deque<Number>st;
int d,s,use[510][5010],pre[510*5010];
int num[5010*510],fir,sec;
inline void Print(int x)
{
    if(x==1)return;
    Print(pre[x]);
    chu("%d",num[x]);
}
inline void BFS()
{
    sec++;
    while(!st.empty())
    {
        int modnow=st.front().mod,sumnow=st.front().sum;
        st.pop_front();fir++;
        _f(i,0,9)
        {
            int modnew=(modnow*10+i)%d,sumnew=sumnow+i;
            if(use[modnew][sumnew])continue;
            if(sumnew>s)continue;
            if((!modnew)&&(sumnew==s))
            {
                num[++sec]=i;pre[sec]=fir;Print(sec);return;
            }
            use[modnew][sumnew]=1;
            num[++sec]=i;pre[sec]=fir;
            st.push_back(Number(modnew,sumnew));
        }
    }
    chu("-1");
}
int main()
{
//    freopen("1.in","r",stdin);
//    freopen("a.txt","w",stdout);
    d=re(),s=re();
    st.push_front(Number(0,0));
    use[0][0]=1;
    BFS();
    return 0;
}
/*
13 50
dp[i][j][k]:表示

这是高精度吗!?
*/
AC

 

posted on 2022-07-26 11:59  HZOI-曹蓉  阅读(53)  评论(0编辑  收藏  举报