UVa 10712 - Count the Numbers

博主最近一直在积累组合计数方面的经验 , 这一次是数位dp......感谢Remilia's文章的帮助


提示:

1. 本题思路是利用数位进行计数 , 但网上也有容斥原理和AC动归的做法(其实博主一开始也想用容斥原理做的)

2. 这种经典的两端点区间的问题 , 可以分化成两个从0开始的区间的差集

3. 状态数组的意义: d[ i ][ j ][ k ][ p ][ q ] 中 , 处理完第i位 , 此时末两位是j , 是否已经比上界小(k=0 , 1) , 是否出现子串(p=0 , 1)  , 是否已经有一位不是0(q=0 , 1)


注意: 转移方程可以自己好好考虑 , 但博主强烈建议使用向前刷表的方式


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <deque>
#include <stack>
#include <algorithm>

using namespace std;
// dp version
typedef long long ll;

ll A, B , N;
int a[20] , an;
ll d[20][100][2][2][2]; // instore the last 2 bits , 2-bit-strategy

ll solve(ll A) // we dont consider 0 here
{
	if(A<0) return 0;
	if(A==0) { if(N==0) return 1; return 0; }
	
	ll nn = N , base=1;
	while(nn) base*=10 , nn/=10; if(N==0) base=10;
	
	an=0;
	memset(a, 0, sizeof(a)); while(A) a[++an] = A%10 , A/=10; an = max(an , 1);
	reverse(a+1, a+1+an);
	memset(d, 0, sizeof(d));
	
	d[0][0][0][0][0] = 1;
	
	for(int i=0;i<an;i++) for(int j=0;j<100;j++) for(int k=0;k<2;k++) for(int p=0;p<2;p++) for(int q=0;q<2;q++) // push forward
	{
		ll& res = d[i][j][k][p][q];
		if(!res) continue;
		for(int jj=0;jj<10;jj++) // what I add for the next blank
		{
			if(k==0 && jj>a[i+1]) continue;
			int kk=-1 , pp=-1 , qq=-1;
			
			kk=k;
			if(k==0 && jj<a[i+1]) kk = 1;
			pp = p;
			if(p==0 && (j*10+jj)%base==N) pp = 1;
			qq = q;
			if(q==0 && jj) qq = 1;
			
			if(qq==0) pp = 0;// in order to prevent the 0 condition
			d[i+1][(j%10)*10+jj][kk][pp][qq] += res;
		}
	}
	ll res =0;
	for(int j=0;j<100;j++) for(int k=0;k<2;k++) res+= d[an][j][k][1][1];
	if(N==0) res++;
	return res; 
}

int main(int argc, char *argv[]) {
	
	while(cin>>A>>B>>N && A!=-1)
		cout<<solve(B)-solve(A-1)<<endl;
	
	return 0;
}


你可能感兴趣的:(组合,dp,uva)