数位dp练习题

随便记录几个题, 防止连数位dp的思路都忘了,2333

数位dp

  • 先将上限各位保存到num数组
  • 从高位到低位dfs(暴力)
  • 记忆化,优雅的暴力,否则就是O(n)的算法了,对单个数据也有很大的加速
  • pos为-1时以及记忆存在时可直接返回
  • 关键:时刻记住判断条件,做到不重不漏

HDU 3652 B-number

B-number

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 10029 Accepted Submission(s): 6011

Problem Description
A wqb-number, or B-number for short, is a non-negative integer whose decimal form contains the sub- string “13” and can be divided by 13. For example, 130 and 2613 are wqb-numbers, but 143 and 2639 are not. Your task is to calculate how many wqb-numbers from 1 to n for a given integer n.

Input
Process till EOF. In each line, there is one positive integer n(1 <= n <= 1000000000).

Output
Print each answer in a single line.

Sample Input
13
100
200
1000

Sample Output
1
1
2
2

题意:找到1~n内能被13整除且包含13的整数的个数。
思路:记忆化,正常dfs,体会很好。

#include "bits/stdc++.h"

using namespace std;

int n;
int num[12];
int dp[12][10][13][2]; //位置,前导,mod13,达到上限

int dfs(int pos, int pre, bool han13, int mod13, bool f) {
    if(pos==-1) return han13&&!mod13;
    if(!f&&dp[pos][pre][mod13][han13]!=-1) return dp[pos][pre][mod13][han13];
    int up=f?num[pos]:9;
    int cnt=0;
    for(int i=0; i<=up; ++i) {
        int mod=(mod13*10+i)%13;
        if(han13||pre*10+i==13) cnt+=dfs(pos-1,i,true,mod,f&&i==up);
        else cnt+=dfs(pos-1,i,false,mod,f&&i==up);
    }
    if(!f) dp[pos][pre][mod13][han13]=cnt;
    return cnt;
}

int main() {
    ios::sync_with_stdio(false);

    memset(dp,-1,sizeof(dp));
    while(cin>>n) {
        int now=0;
        while(n) {
            num[now++]=n%10;
            n/=10;
        }
        cout<

HDU 3709 Balanced Number

据说是经典题目,一开始想着让支点左边的力矩等于右边的力矩,然后发现要枚举好多东西,不好操作;
看了题解发现自己像个SD,直接枚举支点不就好了吗?
同时也发现自己头脑逐渐呆滞。

Balanced Number

Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others)
Total Submission(s): 9191 Accepted Submission(s): 4377

Problem Description
A balanced number is a non-negative integer that can be balanced if a pivot is placed at some digit. More specifically, imagine each digit as a box with weight indicated by the digit. When a pivot is placed at some digit of the number, the distance from a digit to the pivot is the offset between it and the pivot. Then the torques of left part and right part can be calculated. It is balanced if they are the same. A balanced number must be balanced with the pivot at some of its digits. For example, 4139 is a balanced number with pivot fixed at 3. The torqueses are 42 + 11 = 9 and 9*1 = 9, for left part and right part, respectively. It’s your job
to calculate the number of balanced numbers in a given range [x, y].

Input
The input contains multiple test cases. The first line is the total number of cases T (0 < T ≤ 30). For each case, there are two integers separated by a space in a line, x and y. (0 ≤ x ≤ y ≤ 1018).

Output
For each case, print the number of balanced numbers in the range [x, y] in a line.

Sample Input
2
0 9
7604 24324

Sample Output
10
897

#include "bits/stdc++.h"

using namespace std;

typedef long long ll;

ll x, y, nx, ny;
int num[20], p;
ll dp[20][20][4000]; // 数位,pivot(支点),sum的取值(防止负值,加了2000,但是别人的没加也AC了)
void go(ll a) {
    p=0;
    while(a) {
        num[p++]=a%10;
        a/=10;
    }
}

ll dfs(int pos, int pivot, int sum, int f) {
    if(pos<0) return !sum;
    if(!f&&dp[pos][pivot][2000-sum]!=-1) return dp[pos][pivot][2000-sum];
    ll cnt=0;
    int up=f?num[pos]:9; //若x-1<0,则此处up<0,下方循环不会进入
    for(int i=0; i<=up; ++i) {
        int newsum=sum+(pos-pivot)*i;
        cnt+=dfs(pos-1,pivot,newsum,f&&i==up);
    }
    if(!f) dp[pos][pivot][2000-sum]=cnt;
    return cnt;
}

int main() {
    ios::sync_with_stdio(false);

    memset(dp,-1,sizeof(dp));
    int T;
    cin>>T;
    while(T--) {
        cin>>x>>y;
        go(x-1); // 不用担心x-1<0,原因见上方注释
        nx=0;
        for(int i=0; i

HDU 3886 Final Kichiku “Lanlanshu”

这题很烦!!!

先把我自己的代码放这,WA掉的,应该是算重复了,但不知道是哪里,思路跟题解有些差别。

//#include "bits/stdc++.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

using namespace std;

typedef long long ll;

const int mod = 1e8;
int num[102];
ll a, b, len, p;
int dp[102][102][10]; //数位,str上的位置,前导
string l, str;

void sub(int pos) {
    if(num[pos]==0) {
        num[pos]=9;
        sub(pos+1);
    }
    else num[pos]-=1;
}

int dfs(int pos, int pre, int k, bool first, bool f) { // 数位,前导,str上的位置,是否为第一个数位,达到上限
    if(k>=len) return 0;
    if(pos<0) return k==len-1;
    if(!f&&dp[pos][k][pre]!=-1) return dp[pos][k][pre];
    int cnt=0;
    int up=f?num[pos]:9;
    for(int i=0; i<=up; ++i) {
        if(first) {
            if(i) cnt+=dfs(pos-1,i,k,0,f&&i==up);
            else cnt+=dfs(pos-1,i,k,1,f&&i==up);
        }
        else if(pos==0) {
            if(str[k]=='/') {
                if(i>pre) cnt+=dfs(pos-1,i,k,0,f&&i==up);
            }
            else if(str[k]=='-') {
                if(i==pre) cnt+=dfs(pos-1,i,k,0,f&&i==up);
            }
            else {
                if(ipre) cnt+=dfs(pos-1,i,k,0,f&&i==up)+dfs(pos-1,i,k+1,0,f&&i==up);
            }
            else if(str[k]=='-') {
                if(i==pre) cnt+=dfs(pos-1,i,k,0,f&&i==up)+dfs(pos-1,i,k+1,0,f&&i==up);
            }
            else {
                if(i>str) {
        memset(dp,-1,sizeof(dp));
        len=str.size();
        //以下处理第一个数
        cin>>l;
        p=0;
        num[0]=0;
        for(int i=0; i=0; --i) cout<>l;
        p=0;
        num[0]=0;
        for(int i=0; i=0; --i) cout<

下面是摘的一个AC码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//#define LL __int64
#define LL long long
#define eps 1e-12
#define PI acos(-1.0)
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 4010;
const int mod = 1e8;
char s[110],a[110],b[110],dig[110];
int slen,alen;
int dp[110][110][12];
 
bool check(int a, int b, char ch)
{
	if(ch == '/')
		return a < b;
	else if(ch == '-')
		return a == b;
	else
		return a > b;
}
int dfs(int len, int pos, int pre, int up, int first)
{
	if(len == alen)
	{
		return pos==slen;
	}
	if( !up && !first && dp[len][pos][pre] != -1 )
		return dp[len][pos][pre];
	int n = up ? (dig[len]-'0') : 9;
	int res = 0;
	for(int i = 0; i <= n; i++)
	{
		if(first)
			res += dfs(len+1,0,i,up&&i==n,first&&i==0);
		else
		{
			if( pos < slen && check(pre,i,s[pos]) ) 
				res += dfs(len+1,pos+1,i,up&&i==n,0);
 
			else if(pos > 0 && check(pre,i,s[pos-1]) )
				res += dfs(len+1,pos,i,up&&i==n,0);
		}
		res %= mod;
	}
	if(!up && !first)
		dp[len][pos][pre] = res;
	return res;
}
int cal(char x[], int f)
{
	memset(dp,-1,sizeof(dp));
	alen = strlen(x);
	int st = 0;
	while(x[st] == '0')
		st++;
	if(st >= alen)
		return 0;
	if(f == 1) //处理a-1
	{
		for(int i = alen-1; i >= st; i--)
		{
			if(x[i] >= '1')
			{
				x[i]--;
				break;
			}
			else
			{
				x[i] = '9';
			}
		}
	}
	strcpy(dig,x);
	return dfs(st,0,0,1,1);
}

int main()
{
	while(~scanf("%s",s))
	{
		slen = strlen(s);
		scanf("%s %s",a,b);
		printf("%08d\n",((cal(b,0) - cal(a,1)%mod)+mod)%mod );
	}
	return 0;
}


HDU 4507 吉哥系列故事——恨7不成妻

这题,我能说什么呢?调了两小时都没调出来。
然后就看了别人的题解,发现有个地方思路错了。

有三个返回值的dfs,这题综合性很高呀。
不过数位dp好像综合性都比较高。

吉哥系列故事——恨7不成妻

Time Limit: 1000/500 MS (Java/Others) Memory Limit: 65535/32768 K (Java/Others)
Total Submission(s): 7549 Accepted Submission(s): 2426

Problem Description
  单身!
  依然单身!
  吉哥依然单身!
  DS级码农吉哥依然单身!
  所以,他生平最恨情人节,不管是214还是77,他都讨厌!
  
  吉哥观察了214和77这两个数,发现:
  2+1+4=7
  7+7=72
  77=7
11
  最终,他发现原来这一切归根到底都是因为和7有关!所以,他现在甚至讨厌一切和7有关的数!

什么样的数和7有关呢?

如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
  1、整数中某一位是7;
  2、整数的每一位加起来的和是7的整数倍;
  3、这个整数是7的整数倍;

现在问题来了:吉哥想知道在一定区间内和7无关的数字的平方和。

Input
输入数据的第一行是case数T(1 <= T <= 50),然后接下来的T行表示T个case;每个case在一行内包含两个正整数L, R(1 <= L <= R <= 10^18)。

Output
请计算[L,R]中和7无关的数字的平方和,并将结果对10^9 + 7 求模后输出。

Sample Input
3
1 9
10 11
17 17

Sample Output
236
221
0

羡慕别人的代码

#include
#include
 
#define MOD 1000000007LL
 
typedef long long int ll;
 
typedef struct node
{
	ll sum;
	ll qsum;
	ll cnt;
	node():cnt(0),sum(0),qsum(0){}
	node(ll c,ll s,ll qs):cnt(c),sum(s),qsum(qs){}
}node;
 
int a[100];
ll ten[20];
 
node dp[20][10][10];

node dfs(int pos,bool limit,int state,int mo)   //计算dp[pos][state]即pos-1位是state时满足条件的个数
{
	node ans;
	if(pos==-1) 
	{
		if(mo!=0&&state!=0)
			return node(1,0,0);   
		else
			return node(0,0,0);
	}
 
	if(!limit&&dp[pos][state][mo].sum!=0) 
	{
		res=((res%MOD)*(ten[pos]%MOD))%MOD;
		return (((res*res)%MOD*nu[pos][state][mo])%MOD+((2*res)%MOD*sum[pos][state][mo])%MOD+dp[pos][state][mo]%MOD)%MOD;   //在非特殊情况下,直接返回之前已经算好的答案*/
		return dp[pos][state][mo];
	}
	int up=limit?a[pos]:9;
 
 
	for(int i=0;i<=up;i++)
	{
		if(i==7) continue;
		int ss=(state%7+i%7)%7;
		int mm=(mo*10+i)%7;
		
		node tmp=dfs(pos-1,limit&&i==up,ss,mm);
		ll res=(i*ten[pos])%MOD;
		ll k1,k2;
		ans.cnt=(ans.cnt+tmp.cnt)%MOD;   //更新cnt
		ans.sum=(ans.sum+(tmp.sum+(res*tmp.cnt)%MOD)%MOD)%MOD;  //更新sum
		k1=((res*res)%MOD*tmp.cnt)%MOD;   //n * Y^2
		k2=((2*res)%MOD*tmp.sum)%MOD;    //2 * Y * ∑xi 
		
		ans.qsum=(ans.qsum+((k1+k2)%MOD+tmp.qsum%MOD)%MOD)%MOD;
 
 
 
	}
 
	if(!limit) dp[pos][state][mo]= ans;  //dp只记录普通情况下的值(因为特殊情况都要重新算,不能直接返回)
	return ans;
} 
 
 
 
ll solve(ll n)
{
	node ans;
	int pos=0;
	while(n)
	{
		a[pos++]=n%10;
		n=n/10;
	}
	ans=dfs(pos-1,true,0,0);
	return ans.qsum%MOD;  
}
 
 
int main()
{
	int t;
	ll n,m;
	scanf("%d",&t);
	ten[0]=1;
	for(int i=1;i<=18;i++)
	{
		ten[i]=ten[i-1]*10;
	}
	while(t--)
	{
		scanf("%lld%lld",&n,&m);
		printf("%lld\n",(solve(m)-solve(n-1)+MOD)%MOD);
	}
	return 0;
 
}


你可能感兴趣的:(算法(Lazy))