[SCOI 2009]BZOJ 1026 windy数 - 数位dp

传送门

题目大意:自行参考。

题解:

设dp[i][j][2][2][2]表示考虑到第i位,数字是j,是否紧贴上界,历史上是否存在一个差小于2,以及这i位是否全部为0.

为什么要记录最后一个“是否全部为0”呢?原因是如果前i位全部为0那么第i+1位无论是什么数都满足要求;

否则如果第i位是0但是前i-1位不全为0那么意味着第i+1位只能是2及以上。

注意到如果前i位都是0的话那么历史上就不存在一个差小于2,并且一定不会紧贴上界,所以部分状态就不用考虑转移了(因为他们不合法)

转移方程我一共推了5个。

代码:

//SCOI 2009
//BZOJ 1026
#include<iostream>
#include<cstring>
#include<cstdio>
#define gabs(x) (((x)>0)?(x):(-(x)))
#define f(x) ((int)(gabs(x)>=2))
#define MAXN 20
using namespace std;
int a[MAXN],n,dp[MAXN][10][2][2][2];
int getlist(int A)
{
	n=0;
	while(A)
	{
		a[++n]=A%10;
		A/=10;
	}
	for(int i=1;i<=n/2;i++)
		swap(a[i],a[n-i+1]);
	return 0;
}
int getdp(int A)
{
	getlist(A);
	if(!n) return 1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=9;j++)
			dp[i][j][0][0][0]=dp[i][j][0][0][1]
			=dp[i][j][0][1][0]=dp[i][j][0][1][1]
			=dp[i][j][1][0][0]=dp[i][j][1][0][1]
			=dp[i][j][1][1][0]=dp[i][j][1][1][1]=0;
	dp[1][0][0][1][0]=1;
	for(int i=1;i<a[1];i++)
		dp[1][i][0][1][1]=1;
	dp[1][a[1]][1][1][1]=1;
	for(int i=1;i<n;i++)
		for(int j=0;j<=9;j++)
			for(int k=0;k<=9;k++)
			{
				int p=f(k-j),t=(k==a[i+1]);
				dp[i+1][k][0][0][1]+=dp[i][j][0][0][1];
				dp[i+1][k][0][1][k>0]+=dp[i][j][0][1][0];
				dp[i+1][k][0][p][1]+=dp[i][j][0][1][1];
				if(j==a[i]&&k<=a[i+1])
				{
					dp[i+1][k][t][0][1]+=dp[i][j][1][0][1];
					dp[i+1][k][t][p][1]+=dp[i][j][1][1][1];
				}
			}
	int ans=0;
	for(int j=0;j<=9;j++)
		ans+=dp[n][j][0][1][0]+dp[n][j][0][1][1];
	ans+=dp[n][a[n]][1][1][0]+dp[n][a[n]][1][1][1];
	return ans;
}
int main()
{
	int a,b;scanf("%d%d",&a,&b);
	int ans=getdp(b)-getdp(a-1);
	printf("%d\n",ans);return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值