您的位置:首页 > 其它

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

2012-08-25 03:39 381 查看
我以前习惯叫"按位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
;
}

//统计
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));
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: