一道数位dp练习题

参考博客

题意

给定一个n*m的网格图,图上有一个起点和一个终点(起点和终点处没有障碍),以及若干障碍,对于每个数字0-9都将会给出一对数(ai,bi)。对于一个数t,我们从高位到低位扫一遍,对于t的每一个数字i将当前位置行加上ai列加上bi,若从起点开始在模拟过程中能不走出边界且不走到障碍最终到达终点,就说数t对于当前网格图合法。问区间[L,R]内合法的数t个数有多少,答案对1,000,000,007取模。
中途到达终点不算。移动是瞬间完成的。

数据范围

20%的数据1<=L<=R<=10000。
40%的数据1<=L<=R<=1,000,000,000且R-L<=1000000。
60%的数据1<=L<=R<=10^18。
另有10%的数据所有ai=bi=0。
100%的数据0

解法

首先题目的意思是数字0-9分别代表了一种走法,从(x,y)走到(x+ai,y+bi)
一开始可能会有点手足无措,然后思考一下可以发现可以从高到低确定t的每一位.
然后就开始考虑数位dp,从最高位开始往低dp,记录f[now][x][y][flag]表示现在扫到第i位,走到了(x,y)且有无前导零(记录前导零是因为有可能有起点重点重合)的方案数.然后记忆化搜索就可以了.

#include
using namespace std;
const int mod=1000000007;
int n,m;
char mp[55][55];
int dx[10],dy[10];
int sx,sy,tx,ty;
inline bool can(int x,int y) {return (x<=n&&x>=1&&y<=m&&y>=1)&&mp[x][y]!='*';}
bool check(char *x){
	int len=strlen(x+1);
	int alfa=sx,beta=sy;
	for(int i=1;i<=len;i++){
		alfa+=dx[x[i]-'0'];
		beta+=dy[x[i]-'0'];
		if (!can(alfa,beta)) return false;
	}
	return alfa==tx&&beta==ty;
}
int f[505][55][55][2];int a[505];
int dfs(int pos,int x,int y,bool lim,bool zero){
	if (!can(x,y)) return 0;
	if (pos<=0) return !zero&&x==tx&&y==ty;
	if (!lim&&~f[pos][x][y][zero]) return f[pos][x][y][zero];
	int top=lim?a[pos]:9;int res=0;
	for(int i=0;i<=top;i++){
		if (i==0&&zero){(res+=dfs(pos-1,x,y,lim&&i==top,1))%=mod;continue;}
		(res+=dfs(pos-1,x+dx[i],y+dy[i],lim&&i==top,0))%=mod;
	}
	if(!lim)f[pos][x][y][zero]=res;
	return res;
}
int count(char *x){
	int len=strlen(x+1);
	for(int i=1;i<=len;i++) a[i]=x[len-i+1]-'0';
	return dfs(len,sx,sy,1,1);
}
char L[505],R[505];
int main(){
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	memset(f,-1,sizeof f);
	scanf("%d%d",&n,&m);
	scanf("%s%s",L+1,R+1);
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
		char& c=mp[i][j];
		for (c=getchar();c!='1'&&c!='2'&&c!='3'&&c!='*'&&c!=' ';c=getchar());
		if (c=='1') sx=i,sy=j;
		if (c=='2') tx=i,ty=j;
		if (c=='3') sx=tx=i,sy=ty=j;
	}
	for(int i=0;i<=9;i++) scanf("%d%d",&dx[i],&dy[i]);
	printf("%d\n",((count(R)-count(L)+check(L))%mod+mod)%mod);
}

你可能感兴趣的:(数位dp,思维题)