数位dp专辑

1.题目链接:codeforces 55D

【题意】一个数能被它每一位的数字整除(0除外)则为beautiful number ,问[l,r]之间有多少个这样的数

【思路】考虑到每一位的数字只能是1,2,,9,最小公倍数为2520

设数w=x*2520+y,每位数字的最小公倍数为mul,则w%mul=(x*2520+y)%mul=x*2520%mul+y%mul=y%mul,只需要记录每一步过后该数对2520取模即可。

dp[len][y][mul]表示处理到第len位时数对2520的模,及当前所有数字的最小公倍数。可开到dp[20][2520][2520]

由于数字的最小公倍数是有限的,可将第三维2520进一步优化,预处理求出2520所有的约数。

优化为dp[20][2520[60]


/***********************************************************************
    > File Name: cf55d.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月06日 星期一 15时27分54秒
 ************************************************************************/

#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long 
using namespace std;
ll dp[20][2520][60];
int num[20];
int v[60],cnt;
int to[2600];
int gcd(int a,int b)
{
	return b?gcd(b,a%b):a;
}
int lcm(int a,int b)
{
	if(b==0)
		return a;
	return a*b/gcd(a,b);
}
ll dfs(int p,int y,int multi,int flag)
{
//cout<<p<<' '<<y<<' '<<multi<<' '<<flag<<endl;
	if(p==-1)
		return y%multi==0;
	if(!flag&&dp[p][y][to[multi]]!=-1)
		return dp[p][y][to[multi]] ;
	ll res=0;
	int u=flag?num[p]:9;
	for(int i=0;i<=u;i++)
	{
		res+=dfs(p-1,(y*10+i)%2520,lcm(multi,i),flag&&i==num[p]);
	}
	if(!flag)
		dp[p][y][to[multi]]=res;
//cout<<p<<' '<<y<<' '<<multi<<' '<<flag<<endl;
//cout<<res<<endl;
	return res;
}
ll work(ll x)
{
	int i=0;
	while(x)
	{
		num[i++] =x%10;
		x/=10;
	}
	return dfs(i-1,0,1,1) ;
}
int main()
{
	cnt=0;
//	memset(to,0,sizeof(to));
	for(int i=1;i<=2520;i++)//优化第三维
	{
		if(2520%i==0)
		{
			v[cnt]=i;
			to[i]=cnt++;
		}
	}
//	cout<<cnt<<endl;
//	for(int i=0;i<cnt;i++)
//	{
//		cout<<v[i]<<endl;
//	}
	int t;scanf("%d",&t);
	memset(dp,-1,sizeof(dp));
	while(t--)
	{
		
		ll a,b;
		cin>>a>>b;
		cout<<work(b)-work(a-1)<<'\n';
	}
	return 0;
}


2.题目链接:hdu4352

【题意】定义一个数的值为其数字最长上升子序列的长度,例如3746,最长上升序列为 3,4,6,值为3.求[l,r]个中值为k的数有多少个。

【思路】好题。。忍不住看了题解。。求最长上升子序列时,我们需要维护一个序列,表示长度为1的最后一个数的最小值,长度为2的最后一个数的最小值,长度为3的最后一个的最小值。。。这个序列是单调递增的,我们只需要记录下这个序列即可,只有0-9  10个数字,状态压缩!

/*************************************************************************
    > File Name: hdu4352.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月09日 星期四 20时19分09秒
 ************************************************************************/

#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;

ll dp[20][1<<10][11];
int num[20];

int k;
int getlen(int state)
{
	int res=0;
	for(int i=0;i<10;i++)
		res+=(state&(1<<i))!=0;
	return res;
}
int update(int state,int v)
{
	int len=getlen(state);
	if(len==0&&v==0)return 0;
	if(len==0)return 1<<v;
	for(int i=9;i>=0;i--)
	{
		if(state&(1<<i))
			if(i<v)
			return state|(1<<v);
			else break;
	}
	for(int i=0;i<=9;i++)
	{
		if(state&(1<<i))
		{
			if(i>=v)
				return state^(1<<i)|(1<<v);
		}
	}
	return state;
}
ll dfs(int p,int state,int len,int flag)
{

	//cout<<p<<' '<<state<<' '<<len<<' '<<flag<<endl;
	if(p==-1)
		return getlen(state)==len;
	if(!flag&&dp[p][state][len]!=-1)
		return dp[p][state][len];
	if(getlen(state)>len)return dp[p][state][len]=0;
	ll res=0;
	int u=flag?num[p]:9;
	for(int i=0;i<=u;i++)
	{
		int now=update(state,i);
		res+=dfs(p-1,now,len,flag&&i==num[p]);
	}

//	cout<<p<<' '<<state<<' '<<len<<' '<<flag<<endl;
//	cout<<res<<endl;
	if(!flag)
	{
		dp[p][state][len]=res;
	}
	return res;
}
ll work(ll x)
{
	int i=0;
	while(x)
	{
		num[i++]=x%10;
		x/=10;
	}
	return dfs(i-1,0,k,1);
}
int main()
{
	memset(dp,-1,sizeof(dp));
	int t;
	scanf("%d",&t);
	int ca=0;
	while(t--)
	{
		ll a,b;
		scanf("%lld%lld%d",&a,&b,&k);
		printf("Case #%d: ",++ca);
		cout<<work(b)-work(a-1)<<endl;
	}
	return 0;

}

3.题目链接: hdu2089

【题意】含4或62的数为不吉利数,求[l,r]中有多少个吉利的数

【思路】水题。。直接写

<span style="font-size:14px;">/*************************************************************************
    > File Name: hdu2089.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月06日 星期一 15时58分40秒
 ************************************************************************/

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

int num[10];
int len;
int dp[10][2];
int dfs(int p,int flag,int flag2)
{
	if(p==-1)
		return 1;
	if(!flag&&dp[p][flag2]!=-1)
		return dp[p][flag2];
	int u=flag?num[p]:9;
	int res=0;
	for(int i=0;i<=u;i++)
	{
		if(i==2&&!flag2)continue;
		if(i==4)continue;
		res+=dfs(p-1,flag&&i==num[p],i!=6);
	}
	return dp[p][flag2]=res;
}
int work(int x)
{
	memset(dp,-1,sizeof(dp));
	int len=0;
	while(x)
	{
		num[len++]=x%10;
		x/=10;
	}
	return dfs(len-1,1,1);
}
int main()
{
	int n,m;
	while(cin>>n>>m)
	{
		if(n==0&&m==0)
			break;
		printf("%d\n",work(m)-work(n-1));
	}
	return 0;
}</span>

4.题目链接:hdu3555

【题意】跟上面一题差不多,求[1,x]中含49的有多少个

   【思路】同样水题,记录前一个数字是不是4

<span style="font-size:14px;">/*************************************************************************
    > File Name: hdu3555.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月06日 星期一 16时27分38秒
 ************************************************************************/
#include<iostream>
#include<cstring>
#include<cstdio>
#define ull  unsigned long long 
#define ll long long
using namespace std;
int num[25];
ll A[20];
long long dp[25][2];
long long dfs(int p,int flag,int flag4,int ok)
{
//	cout<<p<<" "<<flag<<' '<<flag4<<' '<<ok<<endl;
	if(p==-1)
		return ok;
	if(!flag&&ok)
	{
	//	cout<<"in "<<A[p+1]<<endl;
		return A[p+1];
	}
	if(!flag&&dp[p][flag4]!=-1)
		return dp[p][flag4];
	int u=flag?num[p]:9;
	ll res=0;
	for(int i=0;i<=u;i++)
	{
		res+=dfs(p-1,flag&&i==num[p],i==4,ok||(flag4&&i==9));
	}
	return dp[p][flag4] = res;
}
ull work(ull x)
{

	memset(dp,-1,sizeof(dp));
	int i=0;
	while(x)
	{
		num[i++]=x%10;
		x/=10;
	}
	return dfs(i-1,1,0,0);
}
int main()
{
	int t;
	A[0]=1;
	for(int i=1;i<=18;i++)
	{
		A[i]=A[i-1]*10;
//	cout<<A[i]<<' ';
	}
	scanf("%d",&t);
	while(t--)
	{
		unsigned long long a;
		scanf("%I64u",&a);
printf("%I64u\n",work(a));
		//cout<<work(a)<<endl;
	}
	return 0;
}</span>


5.题目链接:poj3252

【题意】将数化为二进制,0的个数不比1少,求[l,r]中这样的数有多少

【思路】将数转化为2进制,再进行dp,保存当前0,1的个数,注意开头取0的情况

<span style="font-size:14px;">/*************************************************************************
    > File Name: poj3252.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月06日 星期一 19时23分03秒
 ************************************************************************/

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

int dp[35][35][35][2];
int num[35];

int dfs(int p,int num0,int num1,int flag)
{
//	cout<<p<<' '<<num0<<' '<<num1<<' '<<flag<<endl;
	if(p==-1)
		return num0>=num1;
	if(dp[p][num0][num1][flag]!=-1)
		return dp[p][num0][num1][flag];
	int u=flag?num[p]:1;
	int res=0;
	for(int i=0;i<=u;i++)
	{
		res+=dfs(p-1,num1?num0+!i:0,num1+i,flag&&i==num[p]);
	}
//	cout<<"ans "<<endl;
//	cout<<p<<' '<<num0<<' '<<num1<<' '<<flag<<endl;
//	cout<<res<<endl;
	return dp[p][num0][num1][flag]=res;
}
int work(int x)
{
	int i=0;
	while(x)
	{
		num[i++]=x%2;
		x/=2;
	}
//	for(int j=i-1;j>=0;j--)
//		cout<<num[j];
//	cout<<endl;
	memset(dp,-1,sizeof(dp));
	return dfs(i-1,0,0,1);
}

int main()
{
	int a,b;
	while(scanf("%d%d",&a,&b)!=EOF)
	{
		printf("%d\n",work(b)-work(a-1));
	}
	return 0;
}</span>

6.题目链接:hdu3709

【题意】取一个数中的某一数字为支点,左右距离*数字之和相等即为平衡数,例如4139,取3为支点,4*2+1*1=9*1,4139为平衡数求[l,r]间有多少个平衡数

【思路】对于一个数,若其为平衡数,则平衡点必唯一。设取x 点为支点,左侧加权和为Lx,数字和为SUMl;右侧加权和为Rx,数字和为SUMr.该数字为N x,则支点右移一位左侧加权和为Lx+SUMl+Nx,右侧加权和为Rx-SUMr。

观察原左右侧加权和之差,Lx-Rx,现加权和之差,Lx-Rx-(SUMl+Nx+SUMr)=Lx-Rx-sum(所有数字之和)。

发现,对于每一个支点,求得的差都是不一样的,因此平衡点必唯一。若存在平衡点,则Lx-Rx=0,取任意一点为支点,差值必然是sum的倍数!若差值为sum的倍数则必然存在平衡点,最左侧为支点差值<=0,最右侧为支点差值>=0,中间必有平衡点!以最右侧为支点,算出的差值为sum的倍数则该数是平衡数

<span style="font-size:14px;">/*************************************************************************
    > File Name: hdu3709.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月06日 星期一 20时07分21秒
 ************************************************************************/

#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long 
using namespace std;
int num[25];
ll dp[2000][180][20];
ll dfs(int p,int value,int sum,int flag)
{
	//cout<<p<<' '<<value<<' '<<sum<<' '<<flag<<endl;
	if(p==-1)
		return sum==0||value%sum==0;
	if(!flag&&dp[value][sum][p]!=-1)
		return dp[value][sum][p];
	int u=flag?num[p]:9;
	ll res=0;
	for(int i=0;i<=u;i++)
	{
		res+=dfs(p-1,value+i*(p+1),sum+i,flag&&i==num[p]);
	}
	if(!flag)
		dp[value][sum][p]=res;
//	cout<<p<<' '<<value<<' '<<sum<<' '<<flag<<endl;
//	cout<<res<<endl;
	return res;
}
long long work(ll  x)
{
//	cout<<"work "<<endl;
	//	cout<<x<<endl;
	if(x==-1)
		return 0;
	int i=0;
	while(x)
	{
		num[i++]=x%10;;
		x/=10;
	}
	return dfs(i-1,0,0,1);
}
int main()
{
//	freopen("a.txt","r",stdin);
	int t;
	memset(dp,-1,sizeof(dp));
	cin>>t;
	while(t--)
	{
		long long a,b;
		cin>>a>>b;
		if(a>b)
		{
			int tm=b;
			b=a;
			a=tm;
		}
		cout<<work(b)-work(a-1)<<'\n';
	}
	return 0;
}</span>

7.题目链接:hdu3652

【题意】数中出现13,[1,n]中有多少个这样的数

【思路】。。。

<span style="font-size:14px;">/*************************************************************************
    > File Name: hdu3652.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月07日 星期二 00时18分32秒
 ************************************************************************/

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

int num[15];
int dp[12][13][2][2];
int dfs(int p,int yu,int flag1,int flag13,int flag)
{
	if(p==-1)
		return yu==0&&flag13;
	if(!flag&&dp[p][yu][flag1][flag13]!=-1)//flag13也可单列出来,减小一维
		return dp[p][yu][flag1][flag13];
	int u=flag?num[p]:9;
	int res=0;
	for(int i=0;i<=u;i++)
	{
		res+=dfs(p-1,(yu*10+i)%13,i==1,flag13||(flag1&&i==3),flag&&i==num[p]);
	}
//	cout<<p<<' '<<yu<<' '<<flag1<<' '<<flag13<<' '<<flag<<endl;
//	cout<<res<<endl;
	if(!flag)
		dp[p][yu][flag1][flag13]=res;
	return res;
}
int work(int x)
{
	int i=0;
	while(x)
	{
		num[i++]=x%10;
		x/=10;
	}
	return dfs(i-1,0,0,0,1);
}
int main()
{
	memset(dp,-1,sizeof(dp));
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		printf("%d\n",work(n));
	}
	return 0;
}</span>

8.题目链接:hdu4734

【题意】函数  F(x) = A n * 2 n-1 + A n-1 * 2 n-2 + ... + A 2 * 2 + A 1 * 1求[0,b]中f(i)比f(A)小的个数。

【思路】f最大为9*2^9.....+9*2^0=9*(2^10-1)<10*2^10,dp[len][sum]表示剩下长度为len,f值比sum小或相等的个数。奇怪的题目。。。

<span style="font-size:14px;">/*************************************************************************
    > File Name: hdu4734.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月07日 星期二 00时18分32秒
 ************************************************************************/

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int num[12];
int dp[12][5120];
int fa;
int f(int x)
{
	int res=0;
	int v=1;
	while(x)
	{
		res=res+x%10*v;
		x/=10;
		v*=2;
	}
	return res;
}
int A[10];
int dfs(int p,int sum,int flag)
{
//	cout<<p<<' '<<sum<<' '<<flag<<endl;
	if(p==-1)
		return sum>=0;
	if(sum<0)
		return 0;
	if(!flag&&dp[p][sum]!=-1)
		return dp[p][sum];
	int u=flag?num[p]:9;
	int res=0;
	for(int i=0;i<=u;i++)
	{
		res+=dfs(p-1,sum-i*A[p],flag&&i==num[p]);
	}
//	cout<<p<<' '<<sum<<' '<<flag<<endl;
	//cout<<res<<endl;
	if(!flag)
		dp[p][sum]=res;
	return res;
}
int work(int x)
{
	if(x==-1)
		return 0;
	int i=0;
	while(x)
	{
		num[i++]=x%10;
		x/=10;
	}
//	memset(dp,-1,sizeof(dp));
	return dfs(i-1,fa,1);
}
int main()
{
	A[0]=1;
	for(int i=1;i<=9;i++)
		A[i]=A[i-1]*2;
	int t;
	scanf("%d",&t);
	memset(dp,-1,sizeof(dp));
	int T=t;
	while(t--)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		fa=f(a);
//	cout<<"fa: "<<fa<<endl;
		printf("Case #%d: %d\n",T-t,work(b));
	}
	return 0;
}</span>


9.题目链接:hdu4507

【思路】主要还是推平方和的公式  ∑(x+ai)^2=n*x^2+2*x*∑ai+∑ai^2,需要得到子问题的和及平方和及个数。细节地方一定要注意,个数为0与和为0是不一样的,和为0还有可能存在符合条件的数。求个数的时候一定要注意不要取模。。然而取模居然也能AC。。

<span style="font-size:14px;">/*************************************************************************
    > File Name: hdu4507.cpp
    > Author: wanghao
    > Mail: haohaoac@163.com 
    > Created Time: 2015年07月07日 星期二 20时07分53秒
 ************************************************************************/

#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
int mod=1000000007;
ll dp[20][7][7][3];

ll A[20];
int num[20];
void dfs(int p,int sum7,int value7,int flag,ll *sum,ll *sum2,ll *sumn)
{
	if(p==-1)
	{
		*sum=*sum2=0;
		if(sum7==0||value7==0)
			*sumn=0;
		else *sumn=1;
		return ;
	}
	if(!flag&&dp[p][sum7][value7][2]!=-1)
	{
		*sum=dp[p][sum7][value7][0];
		*sum2=dp[p][sum7][value7][1];
		*sumn=dp[p][sum7][value7][2];
		return ;
	}
 	int u=flag?num[p]:9;
	*sumn=*sum=*sum2=0;
	int ok=0;
	for(int i=0;i<=u;i++)
	{
		if(i==7)continue;
		ll a,b,c;
		dfs(p-1,(sum7+i)%7,(value7*10+i)%7,flag&&i==num[p],&a,&b,&c);
		if(c==0)//不判也能ac...但不知道为毛
			continue;
		ok=1;
		*sum2=(*sum2+c%mod*i*A[p]%mod*i*A[p]%mod+b+2*i*A[p]%mod*a%mod)%mod;
		*sum=(*sum +c%mod*i*A[p]+a)%mod;
		*sumn=*sumn+c;//别取模。。但取模也能a
	}
//	cout<<"df "<<p<<' '<<sum7<<' '<<value7<<' '<<flag<<endl;
//	cout<<*sum<<' '<<*sum2<<' '<<*sumn<<endl;
	if(!flag) 
	{
		dp[p][sum7][value7][0]=*sum;
		dp[p][sum7][value7][1]=*sum2;
		dp[p][sum7][value7][2]=*sumn;		
	}
 }
ll work(ll x)
{
//	cout<<"work "<<x<<endl;
	int i=0;
	while(x)
	{
		num[i++]=x%10;
		x/=10;
	}
	ll a,b,c;
	dfs(i-1,0,0,1,&a,&b,&c);
//	cout<<b<<endl;
	return b;
} 
int main()
{
	int t;
	scanf("%d",&t);
	memset(dp,-1,sizeof(dp));
	for(int i=0;i<20;i++)
		A[i]=i==0?1:(A[i-1]*10)%mod;
	while(t--)
	{
		ll a,b;
		cin>>a>>b;
		cout<<(work(b)-work(a-1)+mod)%mod<<'\n';
	}
	return 0;
}</span>

PS: 就是模板题( ⊙ o ⊙ )啊!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值