HDU 4389 X mod f(x)[数位统计dp]

我以前习惯叫"按位dp",貌似一样的.以前都是用记忆化搜索做,转移起来不用多想. 现在学了这个大牛 的写法, 感觉用迭代写也不错.

总结一下:

就是拿到一个上界bound.然后逻辑上将bound按位划分为三份,一份是统计过的,一份是当前统计位,最后一份是未统计位.

从bound的高到低位(a[n~1])进行统计,

统计 i 位时, a[n~i+1]都是统计过的, 都当成a[i](即那一位上最大可能的数码). 然后a[i]是当前统计位, 枚举 0~a[i]-1 这几个可能的数码. 而a[i-1~1]为未统计位, 每次对未统计位进行dp.(即在a[n~i]的限制下, 未统计位有多少种数字可能).


然后这道题, 思路都在代码里了.

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<string>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
inline int Rint() { int x; scanf("%d", &x); return x; }
inline int max(int x, int y) { return (x>y)? x: y; }
inline int min(int x, int y) { return (x<y)? x: y; }
#define FOR(i, a, b) for(int i=(a); i<=(b); i++)
#define FORD(i,a,b) for(int i=(a);i>=(b);i--)
#define REP(x) for(int i=0; i<(x); i++)
typedef long long int64;
#define INF (1<<30)
const double eps = 1e-8;
#define bug(s) cout<<#s<<"="<<s<<" "

//	[a, b]内  x%sigma(xi)=0 的个数.
//	思路:
//	首先区间最大可以为10^9,肯定要dp,归为子问题. 
//	那么上 数位统计dp.
//	肯定有的两维, 数的位数 跟 数各位之和, 然后考虑转移, 再加上两维 模数 跟 余数.
//	则状态为, d[位数][各位和][模数][余数] 表示满足的数的个数.
//	d[len+1][sum+x][mod][(res*10+x)%mod] = sigma( d[len][sum][mod][res] ).
//	然后统计时, 从左到右逐位统计, 如321, 
//	第一位为0,1,2, 即 0xx, 1xx, 2xx. (xx表示任意两位数, 用dp出来的值确定符合的个数.)
//	第二位为0,1, 即 30x, 31x.
//	第三位为0,即 320(有点特殊, 转移到这个状态的是 d[0][0][mod][0], 而且还有一个数321不会被统计到,
//	所以我们把最后一位单独进行统计).

#define MAXN 9
#define MAXSUM (MAXN*9)		//81

int tens[MAXN+2];	// tens[i] = 10^i.

int d[MAXN+2][MAXSUM+2][MAXSUM+2][MAXSUM+2];	//d[位数][各位和][模数][余数]
void dp()
{
	memset(d, 0, sizeof(d));

	//初始化边界, len=1
	FOR(sum, 0, 9)
		FOR(mod, 1, MAXSUM)
			d[1][sum][mod][sum%mod]++;
	//dp
	FOR(len, 1, MAXN-1)		//循环从1开始, 其实算的是 i+1
		FOR(sum, 0, MAXSUM)
			FOR(mod, 1, MAXSUM)
				FOR(res, 0, MAXSUM-1)
					FOR(x, 0, 9)	//枚举增量
	{
		if(sum+x>MAXSUM) break;
		d[len+1][sum+x][mod][(res*10+x)%mod] += d[len][sum][mod][res];
	}
}

int cal(int x)
{
	if(x == 0) return 0;	//特判

	//处理成数组
	int a[MAXN+3];		//1-th
	int n=0;
	int s = 0;	//各位和
	for(int t=x; t; t/=10)
	{
		a[++n] = t%10;
		s+=a[n];
	}

	//统计
	int cnt = 0;
	FOR(mod, 1, 9*n)	//枚举可能的模数, 即最终各位和
	{
		if(mod>MAXSUM) break;	//x=10^9的时候可能会超
		if(mod>x) break;	//剪枝

		int pre = 0;	//前面的和
		int sum = 0;	//当前各位和
		FORD(i, n, 2)		//从高位到低枚举
		{
			int len = i-1;	//剩余位数
			FOR(j, 0, a[i]-1)	//枚举当前位的数码
			{
				if(mod-sum-j<0) break;
				FOR(res, 0, mod-1)		//枚举剩余部分可能的余数
				{
					if((pre+j*tens[len]+res)%mod == 0)
					{
						//bug(len);bug(mod-sum-j);bug(mod);bug(res);bug(d[len][mod-sum-j][mod][res])<<endl;
						cnt += d[len][mod-sum-j][mod][res];
					}
				}
			}
			sum += a[i];
			if(sum>mod) break;
			pre += a[i]*tens[len];
		}
	}
	//bug(cnt)<<endl;
	for(int t=x; ; t--, s--)	//单独处理最低位
	{
		if(t%s == 0) cnt++;
		if(t%10 == 0) break;	//要借位了
	}
	//bug(cnt)<<endl;
	return cnt;
}

void init()
{
	tens[0] = 1;
	FOR(i, 1, MAXN)
		tens[i] = tens[i-1]*10;
	dp();
}

int main()
{
	init();
	int T = Rint();
	FOR(ca, 1, T)
	{
		int A = Rint();
		int B = Rint();
		printf("Case %d: %d\n", ca, cal(B)-cal(A-1));
	}
}


发布了179 篇原创文章 · 获赞 5 · 访问量 20万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览