【整理】一堆好吃の数位dp

我觉得还是把数位dp和数学分开放比较好QuQ

一共11道题_(:з)∠)_ 注孤生啊!

虽然直到现在我也只会写记忆化orz。。。。不过数位dp就到这里吧orz。。1A来得特别爽。。。

(请忽视C题的迷之TLE。。)



【1】CodeForces 55D Beautiful numbers(数位DP+数学)



题意:求一个正整数区间内能被自己的每一位整除的数的个数


人生第一道数位dp 就被虐疯了……(好像也不是特别难?)

首先设单位整数集为s = {0,1,2,3,4,5,6,7,8,9} lcm(s) = 2520 再设当前数a的每一位之间的lcm为t 

显然 lcm(s) % t == 0 所以只要 a % lcm(s) % t == 0 则a符合条件

dp[i][j][k]记录确定到了倒数第i位 之前所有数mod s为j 之前所有数的lcm为k时的符合条件的数 

其中k开不下2520 所以把2520的所有因数离散化一下只有48个


(我也不知道我的代码有没有bug 没交oj= = 我才不会说是因为交不上去呢。。。。)

#include <iostream>
#include <cstring>

using namespace std;

const int mod = 2520;
const int maxb = 21;

long long l, r; 
long long f[maxb][mod + 5][50];
int bit[maxb];
int hash[mod + 5];

inline int gcd(int a, int b)
{
	for(int t = a % b; t; a = b, b = t, t = a % b);
	return b;
}

inline int lcm(int a, int b)
{
	return a / gcd(a, b) * b;
}

long long dfs(int pos, int pre_mod, int pre_lcm, bool flag) // flag : 是否未到上界 
{
	if(pos == 0) return pre_mod % pre_lcm == 0;
	if(flag && ~f[pos][pre_mod][hash[pre_lcm]]) return f[pos][pre_mod][hash[pre_lcm]];
	int mmax = flag ? 9 : bit[pos];
	
	long long ans = 0;
	for(int i = 0; i <= mmax; ++i)
	{
		int next_mod = ((pre_mod * 10) + i) % mod;
		int next_lcm = pre_lcm;
		if(i) next_lcm = lcm(next_lcm, i);
		ans += dfs(pos - 1, next_mod, next_lcm, flag || i < mmax);
	}
	if(flag) f[pos][pre_mod][hash[pre_lcm]] = ans;
	return ans;
}

inline long long solve(long long n)
{
	int len = 0;
	for( ; n; )
	{
		bit[++len] = n % 10;
		n /= 10;
	}
	return dfs(len, 0, 1, 0);
}

int main()
{
	for(int i = 1, cnt = 0; i <= mod; ++i) if(!(mod % i)) hash[i] = ++cnt;
	memset(f, -1, sizeof(f));
	ios::sync_with_stdio(false);
	while(cin >> l >> r) cout << solve(r) - solve(l - 1) << endl;
	return 0;
}


来自小可:

LL dfs(int pos,int mod,int lcm,bool limit)  
{  
   LL ans = 0;  
   if(pos<=0) return mod % lcm == 0;  
   if(!limit && dp[pos][mod][hash[lcm]]!=1) return dp[pos][mod][hash[lcm]];  
   int end = limit ? digit[pos] : 9;  
   for(int i=0;i<=end;i++)  
   {  
      ans += dfs(pos1,(mod*10+i)%2520,i?calc_lcm(lcm,i):lcm,limit && (i==end));  
   }  
   if(!limit) dp[pos][mod][hash[lcm]] = ans;  
   return ans;  
}


【2】bzoj1026 [scoi2009] windy数



数位dp的(经典)水题…………f[pos][pre][limit][flag] 表示当前确定到第pos位 最后一个数字是pre 是否到达上限 是否前导零 的总方案数orz……然后dfs就可以了

窝只会写记忆化搜索orz记得之前看代码好像基本都是写递推的= =。。反正我不会……………………

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>

using namespace std;

int A, B;
int bit[15];
int f[15][15][3][3];

int Abs(int i) { return i > 0 ? i : -i; }

int dfs(int pos, int pre, bool limit, bool flag)
{
	if(!pos) return 1;
	if(f[pos][pre][limit][flag] != -1) return f[pos][pre][limit][flag];
	
	int d = limit ? bit[pos] : 9; 
	int &ans = f[pos][pre][limit][flag] = 0;
	for(int i = 0; i <= d; ++i)
	{
		if(flag || Abs(pre - i) > 1)
			ans += dfs(pos - 1, i, limit && i == bit[pos], flag && !i);
	}
	return ans;
}

inline int solve(int x)
{
	int len = 0;
	for( ; x; )
	{
		bit[++len] = x % 10;
		x /= 10;
	}
	memset(f, -1, sizeof(f));
	return dfs(len, 0, 1, 1);
}

int main()
{
	A = read(); B = read();
	cout << solve(B) - solve(A - 1) << endl; 
	return 0;
}

【3】hdu2089 不要62


统计不含4和62的数 比上面那题还水……

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>

using namespace std;

int A, B;
int bit[10];
int f[10][15][3];

int dfs(int pos, int pre, bool limit)
{
	if(!pos) return 1;
	if(f[pos][pre][limit] != -1) return f[pos][pre][limit];
	
	int d = limit ? bit[pos] : 9; 
	int &ans = f[pos][pre][limit] = 0;
	for(int i = 0; i <= d; ++i)
	{
		if(i - 4 && !(pre == 6 && i == 2) )
			ans += dfs(pos - 1, i, limit && i == bit[pos]);
	}
	return ans;
}

inline int solve(int x)
{
	int len = 0;
	for( ; x; )
	{
		bit[++len] = x % 10;
		x /= 10;
	}
	memset(f, -1, sizeof(f));
	return dfs(len, 0, 1);
}

int main()
{
	ios::sync_with_stdio(false);
	while(cin >> A >> B && A && B)
		cout << solve(B) - solve(A - 1) << endl; 
	return 0;
}

【4】hdu3709 Banlanced Number



题意:以某个数字的某一位为支点 另外两边的数字大小乘以力矩之和相等 为平衡数 求区间内平衡数的个数

枚举平衡点 对于每个平衡点dfs一遍 然后加起来就行了 有点要注意就是对于0计算了len次 所以要去重


#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>

using namespace std;

int T; 
long long A, B;
int bit[21];
long long f[21][21][2005]; 

long long dfs(int pos, int point, int pre, bool limit)
{
	if(!pos) return pre == 0;
	if(pre < 0) return 0;
	if(!limit && f[pos][point][pre] != -1) return f[pos][point][pre];
	
	int d = limit ? bit[pos] : 9; 
	long long ans = 0;
	for(int i = 0; i <= d; ++i)
	{
		ans += dfs(pos - 1, point, pre + (pos - point) * i, (i == d) && limit);
	}
	if(!limit) f[pos][point][pre] = ans;
	return ans;
}

inline long long solve(long long x)
{
	int len = 0;
	for( ; x; )
	{
		bit[++len] = x % 10;
		x /= 10;
	}
	memset(f, -1, sizeof(f));
	
	long long ans = 0;
	for(int i = 1; i <= len; ++i)
	{
		ans += dfs(len, i, 0, 1);
	}
	return ans - len + 1;
	// 当所有数位都为0时 任何支点都合法 去重 
}

int main()
{
	ios::sync_with_stdio(false);
	
	for(cin >> T; T--; )
	{
		cin >> A >> B;
		cout << solve(B) - solve(A - 1) << endl;
	}
	return 0;
}


【5】[Scoi2014]方伯伯的商场之旅

题解:http://blog.csdn.net/qq_21841245/article/details/44243773
这道神题搞了我一下午 = =


【6】HDU3555 Bomb


题意:统计[1, n]内含有49的数的个数

dp[pos][flag] :
flag == 1 前一个数为4 
flag == 2 已包含49
flag == 0 其余情况下

(突然好好奇我把stdio的同步关了是怎么用getchar AC了的…………)

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

long long LL()
{
	long long sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

int T;
long long N; 
int bit[25];
long long dp[25][3];

long long dfs(int pos, int flag, bool limit)
{
	if(pos == 0) return flag == 2;
	if(!limit && ~dp[pos][flag]) return dp[pos][flag];
	
	int d = limit ? bit[pos] : 9;
	long long ans = 0ll;
	for(int i = 0; i <= d; ++i)
	{
		int next = flag;
		if(!flag && i == 4) next = 1;
		else if(flag == 1 && i == 9) next = 2;
		else if(flag == 1 && i != 4) next = 0;
		ans += dfs(pos - 1, next, limit && i == d);
	}
	if(!limit) dp[pos][flag] = ans;
	return ans;
}

long long solve(long long N)
{
	int len = 0;
	while(N)
	{
		bit[++len] = N % 10;
		N /= 10;
	}
	memset(dp, -1, sizeof(dp));
	return dfs(len, 0, 1);
}

int main()
{
	ios::sync_with_stdio(false);
	for(T = read(); T--; )
	{
		N = LL();
		cout << solve(N) << endl;
	}
	return 0;
}


【7】HDU3565 Bi-peak Number


题意:各位数字先增后减且位数大于等3且第一位非零的数称为峰值数 两个峰值数连在一起是一个bi-peak数 求[l, r]之间bi-peak数的各位数字之和的最大值

这个玩意不能相减。。所以必须L和R一起来dp。。。然后这玩意有7种状态。。。要一种一种考虑。。。
状态定义见代码 (特判相当之恶心 = = 还有我写switch case就一直TLE 改成if else竟然就过了。。)

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

typedef unsigned long long LL;

int T; 
LL L, R;
int bitl[25], bitr[25];
int dp[25][11][7];
/* 
	0: 前面都是0
	1: 第一个上坡
	2: 第一个上坡/下坡
	3: 第一个下坡
	4: 第二个上坡                    
	5: 第二个上坡/下坡
	6: 第二个下坡 
*/

inline int max(int a, int b) { return a > b ? a : b; }

int dfs(int pos, int last, int flag, bool liml, bool limr)
{
	if(!pos) return flag == 6 ? 0 : -1;
	if(!liml && !limr && ~dp[pos][last][flag]) return dp[pos][last][flag];
	
	int e = liml ? bitl[pos] : 0;
	int d = limr ? bitr[pos] : 9;
	
	int res = -1;
	for(int i = e; i <= d; ++i)
	{
		int next = 0;
    	if(flag == 0 && i) next = 1;
    	else if(flag == 1) next = i > last ? 2 : -1;
    	else if(flag == 2) 
	    {
		    if(i < last) next = 3;
		    else if(i == last) next = -1;
		    else next = 2;
	    }
	    else if(flag == 3)
	    {
		    if(i > last) next = 4;
			else if(i == last) next = i ? 4 : -1;
		    else next = 3;
	    }
    	else if(flag == 4) next = i > last ? 5 : -1;
    	else if(flag == 5) 
	    {
		    if(i < last) next = 6;
		    else if(i == last) next = -1;
		    else next = 5;
	    }
    	else if(flag == 6) next = i < last ? 6 : -1;
    	
    	if(~next)
    	{
    		int temp = dfs(pos - 1, i, next, liml && i == e, limr && i == d);
    		if(~temp) res = max(res, i + temp);
    	}
	}
	if(!liml && !limr) return dp[pos][last][flag] = res;
	return res;
}

int main()
{
	ios::sync_with_stdio(false); 
	int cas = 0;
	memset(dp, -1, sizeof(dp)); 
	for(cin >> T; T--; )
	{
		cin >> L >> R;
		
		int len = 0;
		while(R)
		{
			++len;
			bitl[len] = L % 10;
			bitr[len] = R % 10;
			L /= 10, R /= 10;
		}
		
		cout << "Case " << ++cas << ": ";
		int temp = dfs(len, 0, 0, 1, 1);
		if(~temp) cout << temp << endl; else cout << 0 << endl;
	}
	
	return 0;
}


【8】UVALive 4031 Integer Transmission


题意:有一个数字k 然后它的二进制位有n位(不足n位前面用0补足) 然后它从前往后一位一位传递给你 但是传递的时候可能会有延迟0~d位 当几位数字同时到达时可以随意排列 求最终能排列出来的数的最大值 最小值 和 个数

我感觉这道题。。完全靠YY。。。我各种乱搞把样例过了就交了。。。就A了。。。。。
(我该说样例良心还是我的YY能力见长?)(好像这两者没什么关系。。)

首先。。求最大值和最小值。。
很明显最大值要尽量把1放前面 那么就优先考虑能不能填1 不能填1再填0 我们从高位往低位填 那么对于当前位 当“没有0可以填了”或者“剩余的最高位的0右移d位后能比剩余的最高位的1更靠右”时 这个位置就可以填1 其余情况只能填0
然后求最小值的时候反过来就行了

然后就统计个数 有了前面的思路这个就直接照着来就行了
dp[i][j] 表示剩余i个1没填 j个0没填 可以填1的时候就把方案数加上填1的 可以填0的时候就加上填0的 判断方法和上面是一样的
 

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

typedef unsigned long long LL;

int N, D;
LL K; 
int bit0[70], bit1[70];
LL dp[70][70]; //dp[i][j] : 剩余i个0 j个1 
LL maxn, minn;
int cnt0, cnt1;

inline void getmin_max()
{
	int p = cnt0, q = cnt1;
	for(int i = 1; i <= N; ++i)
	{
		if( !p || (q && bit0[p] - D <= bit1[q]) ) { maxn = maxn << 1 | 1; --q; }
		else { maxn <<= 1; --p; } 
	}
	p = cnt0, q = cnt1;
	for(int i = 1; i <= N; ++i)
	{
		if( !q || (p && bit1[q] - D <= bit0[p]) ) { minn <<= 1; --p; }
		else { minn = minn << 1 | 1; --q; }
	}
}

LL dfs(int num0, int num1)
{
	if(!num0 && !num1) return 1;
	if(~dp[num0][num1]) return dp[num0][num1];
	
	LL &ans = dp[num0][num1] = 0ll;
	if( !num1 || (num0 && bit1[num1] - D <= bit0[num0]) ) ans += dfs(num0 - 1, num1);
	if( !num0 || (num1 && bit0[num0] - D <= bit1[num1]) ) ans += dfs(num0, num1 - 1);
	
	return ans;
}

int main()
{
	ios::sync_with_stdio(false); int cas = 0;
	while(cin >> N && N)
	{
		cin >> D >> K;
		cnt0 = 0, cnt1 = 0, maxn = 0, minn = 0; 
		for( ; K; K >>= 1)
		{
			if(K & 1) bit1[++cnt1] = cnt1 + cnt0;
			else bit0[++cnt0] = cnt1 + cnt0;
		}
		while(cnt1 + cnt0 < N) bit0[++cnt0] = cnt1 + cnt0; 
		getmin_max();
		
		memset(dp, -1, sizeof(dp));
		cout << "Case " << ++cas << ": " << dfs(cnt0, cnt1) << " " << minn << " " << maxn << endl;
	}
	return 0;
}


【9】POJ 1351 Number of Locks


题意:统计n位的数字满足以下条件的个数 
①每一位都由1~4组成
②至少有相邻的两位差值大于2
③所有位至少由三个以上的数字组成

dp[pos][last][S][flag] : 剩余pos位 上一位数字为last 1~4的选取情况(状压) 是否满足②


#include <cstdio>
#include <iostream>
#include <cstdlib> 
#include <cstring>

using namespace std;

int read()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

int N;
long long dp[20][5][20][2];

inline bool check(int S){ return S == 16 || S == 15 || S == 13 || S == 11 || S == 8; }

long long dfs(int pos, int last, int S, bool flag)
{
	if(!pos) return check(S) && flag;
	if(~dp[pos][last][S][flag]) return dp[pos][last][S][flag];
	
	long long &ans = dp[pos][last][S][flag] = 0;
	for(int i = 1; i <= 4; ++i)
	{
		bool next = flag; int nextS = S | (1 << (i - 1));
		if(last && abs(last - i) > 2) next = 1;
		ans += dfs(pos - 1, i, nextS, next);
	}
	
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	memset(dp, -1, sizeof(dp));
	while(cin >> N && ~N) cout << N << ": " << dfs(N, 0, 0, 0) << endl;
	return 0;
}

然后 这道题是可以打表的 = =

#include <cstdio>
#include <iostream>

using namespace std;

char s[][15] = {"0", "0", "0", "8", "64", "360", "1776", "8216", "36640", "159624", "684240", "2898296", "12164608", "50687208", "209961648", "865509848", "3553389280", "14538802248"};

int main()
{
	ios::sync_with_stdio(false); int N;
	while(cin >> N && ~N) cout << N << ": " << s[N] << endl;
	return 0;
}


【10】HDU3652B-number


题意:统计1~n中数字里含有13且能被13整除的数的个数

嗯……没什么好说的……判断是否整除是可以一位一位判断的 就像小学竖式除法一样

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;


typedef long long LL;

int bit[13];
LL N, dp[13][15][3];

LL dfs(int pos, int mod, int flag, bool limit)
{
	if(!pos) return !mod && flag == 2;
	if(!limit && ~dp[pos][mod][flag]) return dp[pos][mod][flag];
	
	int d = limit ? bit[pos] : 9;
	LL ans = 0ll;
	
	for(int i = 0; i <= d; ++i)
	{
		int next_flag = flag;
		if(!flag && i == 1) next_flag = 1;
		else if(flag == 1 && i == 3) next_flag = 2;
		else if(flag == 1 && i != 1) next_flag = 0;
		int next_mod = (mod * 10 + i) % 13;
		ans += dfs(pos - 1, next_mod, next_flag, limit && i == d);
	}
	
	if(!limit) return dp[pos][mod][flag] = ans;
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	
	memset(dp, -1, sizeof(dp));
	while(cin >> N)
	{
		int len = 0;
		while(N)
		{
			bit[++len] = N % 10;
			N /= 10;
		}
		cout << dfs(len, 0, 0, 1) << endl;
	}
	
	return 0;
}


【11】URAL1057 Amount of Degrees


题意:统计[L, R]中可以表示成k个b的n次方(n随意是多少)的数的个数

拿计算器乱写几个数算一下就会发现这道题其实就是把它化成b进制数后有k位为1其余位均为0的数的个数。。
然后乱搞就行了……应该怎么写都能A吧。。我的dp[i][j]表示剩余i个0 j个1时的个数。。

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

int L, R, K, B, bit[35];
int dp[35][35];

int dfs(int num0, int num1, bool limit)
{
	if(!num0 && !num1) return dp[num0][num1] = 1;
	if(!limit && ~dp[num0][num1]) return dp[num0][num1];
	
	bool flag = limit && !bit[num0 + num1];
	int ans = 0;
	if(num0) ans += dfs(num0 - 1, num1, flag);
	if(num1 && !flag) ans += dfs(num0, num1 - 1, limit && bit[num0 + num1] == 1);
	
	if(!limit) return dp[num0][num1] = ans;
	return ans;
}

inline int solve(int x)
{
	int len = 0;
	while(x)
	{
		bit[++len] = x % B;
		x /= B;
	}
	return dfs(len - K, K, 1);
}

int main()
{
	ios::sync_with_stdio(false); 
	cin >> L >> R >> K >> B;
	memset(dp, -1, sizeof(dp));
	cout << solve(R) - solve(L - 1) << endl;
	
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值