题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=3652
题目大意:
求1到n之间含有13且能被13整除的个数。
解题思路:
就是“不要49”的加强版,在设置状态时需要多用一维,用于判断是否整除13。很容易的可以想到,多出来的一维可以表示当前位除以13的余数。
这样状态设置:dp[pos][mod][status] ,dp[i][mod][0]为到第i位之前不含13且末尾不为1,dp[i][mod][1]为到i位之前不含13且末尾为1,dp[i][mod][2]为含13的。
真的就和“不要49“、“不要62”是一样的。。。那么目标状态就是dp[i][0][2]。
对于每一位,modx=(mod*10+i)%13,这其实和我们手算除法的步骤是一样的。
代码如下:
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
int dp[15][15][3];
int a[15];
int dfs(int pos,int mod,int status,int limit)
{
if(pos<1)
return mod==0&&status==2;//目标状态
if(!limit&&dp[pos][mod][status] != -1)
return dp[pos][mod][status];//记忆化搜索的结果,若记录过了,直接用就好
int next = limit? a[pos]:9;//上一位是否达到了限制值,如果是,那么本位最大只能到a[pos],否则,可以为9.
int ans = 0;
for(int i=0;i<=next;i++)
{
int modx=(mod*10+i)%13;
int statusx=status;
if(status==0&&i==1)
statusx=1;
if(status==1&&i!=1&&i!=3)
statusx=0;
if(status==1&&i==3)
statusx=2;
ans += dfs(pos-1,modx,statusx,limit&&(i==next));
}
if(!limit)
dp[pos][mod][status] = ans;//记忆化
return ans;
}
int main()
{
int len,n;
while(cin>>n)
{
memset(a,0,sizeof(a));
memset(dp,-1,sizeof(dp));
len=0;
while(n!=0)
{
a[++len]=n%10;
n /= 10;
}
cout<<dfs(len,0,0,1)<<endl;
}
return 0;
}
网上流传的一种模板以及套该模板写这题:
// pos = 当前处理的位置(一般从高位到低位)
// pre = 上一个位的数字(更高的那一位)
// status = 要达到的状态,如果为1则可以认为找到了答案,到时候用来返回,
// 给计数器+1。
// limit = 是否受限,也即当前处理这位能否随便取值。如567,当前处理6这位,
// 如果前面取的是4,则当前这位可以取0-9。如果前面取的5,那么当前
// 这位就不能随便取,不然会超出这个数的范围,所以如果前面取5的
// 话此时的limit=1,也就是说当前只可以取0-6。
//
// 用DP数组保存这三个状态是因为往后转移的时候会遇到很多重复的情况。
int dfs(int pos,int pre,int status,int limit)
{
//已结搜到尽头,返回"是否找到了答案"这个状态。
if(pos < 1)
return status;
//DP里保存的是完整的,也即不受限的答案,所以如果满足的话,可以直接返回。
if(!limit && DP[pos][pre][status] != -1)
return DP[pos][pre][status];
int end = limit ? DIG[pos] : 9;
int ret = 0;
//往下搜的状态表示的很巧妙,status用||是因为如果前面找到了答案那么后面
//还有没有答案都无所谓了。而limti用&&是因为只有前面受限、当前受限才能
//推出下一步也受限,比如567,如果是46X的情况,虽然6已经到尽头,但是后面的
//个位仍然可以随便取,因为百位没受限,所以如果个位要受限,那么前面必须是56。
//
//这里用"不要49"一题来做例子。
for(int i = 0;i <= end;i ++)
ret += dfs(pos - 1,i,status || (pre == 4 && i == 9),limit && (i == end));
//DP里保存完整的、取到尽头的数据
if(!limit)
DP[pos][pre][status] = ret;
return ret;
}
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
int dp[15][15][15][3];
int a[15];
int dfs(int pos,int pre,int mod,int status,int limit)
{
if(pos<1)
return mod==0&&status==1;
if(!limit&&dp[pos][pre][mod][status] != -1)
return dp[pos][pre][mod][status];
int next = limit? a[pos]:9;
int ans = 0;
for(int i=0;i<=next;i++)
{
int modx=(mod*10+i)%13;
// int statusx=status;
// if(status==0&&i==1)
// statusx=1;
// if(status==1&&i!=1&&i!=3)
// statusx=0;
// if(status==1&&i==3)
// statusx=2;
ans += dfs(pos-1,i,modx,status||(pre==1&&i==3),limit&&(i==next));
}
if(!limit)
dp[pos][pre][mod][status] = ans;
return ans;
}
int main()
{
int len,n;
while(cin>>n)
{
memset(a,0,sizeof(a));
memset(dp,-1,sizeof(dp));
len=0;
while(n!=0)
{
a[++len]=n%10;
n /= 10;
}
cout<<dfs(len,0,0,0,1)<<endl;
}
return 0;
}