reverse 题解
注意到本题数据范围较大且与数位有关,考虑数位 DP 。
我们发现对于每个询问,我们可以将第一个条件拆开之后差分,可以先从后往前 DP ,预处理出末尾满足 $ L \le \operatorname{reverse}(n) \le R $ 的个数,之后使用试填法填数即可。具体地,在预处理时,处理出顶到上界,顶到下界,以及必定满足 $ L \le \operatorname{reverse}(n) \le R $ 的方案数,不妨假定 \(L\)和\(R\)位数相等。
显然因为我们是从后向前 DP ,所以假如有一个填法满足
在填到 \(k\) 时,无论后面如何填,一定满足 $ L \le \operatorname{reverse}(n) \le R $,反之如果
那么无论后面填什么,一定不满足题意
考虑进行分讨,对于每个 \(k\) 分别处理 \(L_{k,len} = \operatorname{reverse}(n)_{k,len}\),\(R_{k,len} = \operatorname{reverse}(n)_{k,len}\),\(L_{k,len} \le \operatorname{reverse}(n)_{k,len} \le R_{k,len}\) 三种情况的方案数,显然前两种都是\(1\),最后一种是 \(R_{k,len}-L_{k,len}-1\),边界情况自行特判,这根本不用进行 DP,我们可以直接 \(O(len)\) 预处理
填数的时候,根据已经填的几位数reverse之后和 \(L\),\(R\) 的后几位大小关系,确定哪些情况是正确的,如果满足大于等于 \(L\),则可以取到上述第一种情况,满足小于等于 \(R\),则可以取到上述第二种情况。至此,我们完成了对位数相同的 \(L\),\(R\) 的计算。
对于位数不相同的情况,为了避免前导零的干扰,我们可以枚举可能的每个位数计算,这个算法复杂度 \(O(Tu\log^2 L)\) ,其中 \(u\) 是采用的进制,这里取 \(10\)。
代码:
#include <bits/stdc++.h>
#define llt unsigned long long
using namespace std;
llt t,a,b,pw[21],dp[22][5],pre[22],len,la,lb;
llt get(llt x,llt index){if(index<1||index>20) return 0;return x/pw[index-1]%10;}
llt get_head(llt x,llt index){return x/pw[index-1];}
llt Q(llt now){llt cnt=0;while(now){now/=10,cnt++;}return cnt;}
void DP(llt L,llt R)
{llt u,l,r;memset(pre,0,sizeof(pre));memset(dp,0,sizeof(dp));dp[0][0]=0-1;dp[0][1]=dp[0][2]=1;for(int i=1;i<=len;i++){l=get(L,len-i+1);r=get(R,len-i+1);dp[i][0]=get_head(R,len-i+1)-get_head(L,len-i+1)-1;dp[i][1]=dp[i][2]=1;}
}
llt GET(llt p,llt L,llt R)
{llt u,l,r,sum=0,now=0,now1,now2,last1=2,last2=2,tot=0; for(int i=len;i>=1;i--){u=get(p,i);l=get(L,len+1-i),r=get(R,len+1-i);for(int j=0;j<u;j++){if(sum==0&&j==0) continue;if(j<l) now1=1;if(j==l) now1=last1;if(j>l) now1=2;if(j<r) now2=2;if(j==r) now2=last2;if(j>r) now2=1;tot+=dp[i-1][0];if(now1==2) tot+=dp[i-1][1];if(now2==2) tot+=dp[i-1][2];}if(u<l) last1=1;if(u==l) last1=last1;if(u>l) last1=2;if(u<r) last2=2;if(u==r) last2=last2;if(u>r) last2=1;sum+=u;}return tot;
}
void work(llt L,llt R)
{llt maxx=Q(R),to=Q(L),x,y,ans=0;for(int i=maxx;i>=to;i--){len=i;if(i==maxx) x=R;else x=pw[i]-2,ans++;if(i==to) y=L;else y=pw[i-1];DP(L,x);ans+=GET(x+1,L,x)-GET(y,L,x);}printf("%llu\n",ans);
}
int main()
{#ifdef LOCALfreopen("1.in","r",stdin);freopen("1.out","w",stdout);#endifpw[0]=1;for(int i=1;i<=20;i++) pw[i]=pw[i-1]*10; scanf("%llu%llu%llu",&t,&a,&b);while(t--){scanf("%llu%llu",&a,&b);if(b+1==0) b--;//为了避免炸ull,在MAX_ull不可能取到情况下的特判work(a,b);}return 0;
}
为了通过加强版数据,我们需要继续优化复杂度,首先我们发现 GET 里面对于 \(j\) 的枚举完全可以避免,接着又发现中间的 \(l\),\(r\) 都是 \(10^k\) 和 \(10^{k+1}-1\) ,此时因为 \(R\) 的位数比我们多一位,所以只剩下 \(L\) 的限制,所有 \(len\) 位(包含前导零)大于等于 \(L\) 且没有末尾零的数翻转过来都是一个合法的数,直接累加答案就好了,GET 只需要跑两次,复杂度 \(O(T\log R)\),可以通过加强版数据。
代码:
#include <bits/stdc++.h>
#define llt unsigned long long
#define Il __always_inline
using namespace std;
llt seed,last,pw[21],b,a;int t,len;
Il int Q(llt now){int cnt=0;for(;now;now/=10,++cnt);return cnt;}
Il llt GET(llt p,llt L,llt R)
{int u,l,r,last1=2,last2=2;llt tot=0; for(int i=len;i>=1;--i){u=p/pw[i-1]%10;l=L/pw[len-i]%10,r=R/pw[len-i]%10;if(last1==2&&l<u) ++tot;if(last2==2&&r<u) ++tot; if(u>l+1) tot+=u-l-1;tot+=min(r,u);tot+=(R/pw[len-i+1]-L/pw[len-i+1]-1)*(u-1);if(u<l) last1=1;else if(u>l) last1=2;if(u<r) last2=2;else if(u>r) last2=1;}return tot;
}
Il void work(llt L,llt R)
{int maxx=Q(R),to=Q(L);llt x,y,ans=0;for(int i=maxx;i>=to;--i){len=i;if(i==maxx) x=R;else x=pw[i]-2,++ans;if(i==to) y=L;else y=pw[i-1];if(i!=maxx&&i!=to) ans+=x-y+1-(L-L/10)+(L%10!=0);else ans+=GET(x+1,L,x)-GET(y,L,x);}printf("%llu\n",ans);
}
int main()
{#ifdef LOCALfreopen("1.in","r",stdin);freopen("std.out","w",stdout);#endifpw[0]=1;for(register int i=1;i<=19;++i) pw[i]=pw[i-1]*10;pw[20]=0-1; scanf("%llu%llu%llu",&t,&a,&b);while(t--){scanf("%llu%llu",&a,&b);if(b+1==0) b--;work(a,b);}return 0;
}