Problem G: 等凹数字
Description
定义一种数字称为等凹数字,即从高位到地位,每一位的数字先非递增再非递减,不能全部数字一样,且该数是一个回文数,即从左读到右与从右读到左是一样的,仅形成一个等凹峰,如543212345,5544334455是合法的等凹数字,543212346,123321,111111不是等凹数字。现在问你[L,R]中有多少等凹数字呢?
Input
第一行一个整数T,表示数据的组数。
接下来T行每行俩个数字L和R,(1<=L<=R<=1e18)
Output
输出一个整数,代表[L,R]中有多少等凹数字
Sample Input
Sample Output
HINT
小于等于2位的数字无凹峰
解题思路:
一道也许对于大佬来说是稍难的数位dp题, 但对我来说是算很难了。
一开始想的是把数拆成一半,去搜索前一半,记录是否递减的状态,然后在把另一半加上去判断是否大于上届,但是这样就不能记忆化了, gg。
这题有三个状态记录,是否出现递减,是否出现递增, 是否是回文串,前两个状态好说,判断回文串的时候我们需要用一个数组记录自己枚举的数,然后当pos小于len/2的时候,每一位都去比较下。但是问题来了,len是不确定的,并不是一开始那个len,如果想到怎么求len就好做了,为什么呢,因为去搜索的时候会出现一些签到零,知道为什么其实也不难了,我们再加一个状态记录当前位是否是前导零,如果是,每次把len-1,当出现不是前导零的数的时候,len就固定不变了,这样就可以做了。
代码实现起来还是比较麻烦的,许多细节需要注意,比如len的初始值要协调好len/2和pos的比较,判断回文串的时候i和对称位(需要用len计算)的数的比较。好久没写数位dp,以及自己手残,码了好久才出来,
代码:
#include <bits/stdc++.h>
using namespace std;
int num[22];
int dnum[22];
long long dp[20][20][11][2][2][2][2];
int cal(long long x)
{
int i=0;
while(x>0)
{
num[i++]=x%10;
x/=10;
}
return i;
}
long long dfs(int pos, bool ismax, int len, int pre, bool up, bool down, bool ok, bool pz)
{
if(!ok)return 0;
long long ret=0;
if(pos<0)
{
if(down && up && ok) return 1;
else return 0;
}
if(!ismax && dp[pos][len][pre][up][down][ok][pz]>=0)
{
return dp[pos][len][pre][up][down][ok][pz];
}
int i, j;
int bnd=ismax?num[pos]:9;
for(i=0; i<=bnd; i++)
{
dnum[pos]=i;
if(pz){ret+=dfs(pos-1, ismax && i==num[pos], len-(pz&&i==0), i, up, down, ok, pz&&i==0);continue;} //仍然处于前导零位,继续递归求len
if(pre==i)
{
if(ok && pos<len/2) //当pos小于len/2的时候说明到了数的后半段了,需要去判断是否满足回文串
{
ret+=dfs(pos-1, ismax && i==num[pos], len, i, up, down, ok&&dnum[len-pos-1]==i, pz);
}
else ret+=dfs(pos-1, ismax && i==num[pos], len, i, up, down, ok, pz);
}
else if(pre<i)
{
if(!down)continue; //递增必须出现在递减出现之后
if(ok && pos<len/2)
{
ret+=dfs(pos-1, ismax && i==num[pos], len, i, 1, down, ok&&dnum[len-pos-1]==i, pz);
}
else ret+=dfs(pos-1, ismax && i==num[pos], len, i, 1, down, ok, pz);
}
else if(pre>i)
{
if(up)continue;
if(ok && pos<len/2)ret+=dfs(pos-1, ismax && i==num[pos], len, i, 0, 1, ok && dnum[len-pos-1]==i, pz);
else ret+=dfs(pos-1, ismax && i==num[pos], len, i, 0, 1, ok, pz);
}
}
if(!ismax)return dp[pos][len][pre][up][down][ok][pz]=ret;
else return ret;
}
int main()
{
memset(dp, -1, sizeof dp);
int t, i, j, x, y;
long long l, r, ll, rr;
cin>>t;
while(t--)
{
scanf("%lld%lld", &l, &r);
int len=cal(l-1);
ll=dfs(len-1, 1, len, 0, 0, 0, 1, 1);
len=cal(r);
rr=dfs(len-1, 1, len, 0, 0, 0, 1, 1);
printf("%lld\n", rr-ll);
}
}