洛谷[USACO14OPEN] Odometer S
题目大意
当一个数的每一位中有至少一半的数字相同,那么这个数就是一个有趣的数。求区间 [ L , R ] [L,R] [L,R]中有多少个有趣的数。
100 ≤ L ≤ R ≤ 1 0 18 100\leq L\leq R\leq 10^{18} 100≤L≤R≤1018
题解
这道题很容易能想到要用数位DP。
将 [ L , R ] [L,R] [L,R]分为 [ 1 , L − 1 ] [1,L-1] [1,L−1]和 [ 1 , R ] [1,R] [1,R],分别求出答案后作差即可得到最终答案。也就是说,我们只要想办法求出区间 [ 1 , v ] [1,v] [1,v]中的有趣的数的数量即可。设 v v v的位数为 d d d。
先枚举这相同的一半位置的数字 i i i,设 f j , k , 0 / 1 / 2 f_{j,k,0/1/2} fj,k,0/1/2表示 v v v的从低到高的前 j j j中有 k k k位于 i i i相同,这个数与 v v v的前 j j j位相比是小于、等于还是大于。然后对于每种情况考虑转移即可。
为了避免统计含有前导零的数字,我们需要用 g g g来存储 f f f中各个状态的数的数量,以便于统计最后的答案。
但是,这样可能会重复计算多个数,我们要将重复计算的贡献减去。因为满足题意的数需要有至少一半的数字相同,所以这样的数中只会恰好有两种数字。当位数小于 d d d时,用组合数求出重复计算的贡献即可;当位数等于 d d d时,因为有一些数字大于 v v v,没有计算贡献,但如果用组合数计算贡献的话,会把这些当做重复的贡献来减去。所以我们要用 d f s dfs dfs把所有可能的数求出,在这些数中找出小于等于 d d d的并减去其重复计算的贡献。因为这样的数中只会恰好有两种数字,所以每次 d f s dfs dfs只会查找 2 d 2^d 2d个数。
数位DP的时间复杂度为 O ( d 3 ) O(d^3) O(d3),减去重复贡献的时间复杂度为 O ( 10 ⋅ 2 d ) O(10\cdot 2^d) O(10⋅2d),所以总时间复杂度为 O ( d 3 + 10 ⋅ 2 d ) O(d^3+10\cdot 2^d) O(d3+10⋅2d)。
这个方法跑得比较快,目前在洛谷上是最优解。
code
#include<bits/stdc++.h>
using namespace std;
int v1,v[25];
long long L,R,ct,ansL,ansR,wt,jc[20],f[25][25][3],g[25][25][3];
void dd(long long x){
v1=0;
while(x){
v[++v1]=x%10;x/=10;
}
}
int gt(int i,int j){
if(i<j) return 0;
if(i==j) return 1;
return 2;
}
void dfs(long long now,int w,int hv,long long bg){
if(w==1){
if(hv==0&&now<=bg) ++ct;
return;
}
dfs(now*10+wt,w-1,hv-1,bg);
dfs(now*10+v[v1],w-1,hv+1,bg);
}
int main()
{
scanf("%lld%lld",&L,&R);
jc[0]=1;
for(int i=1;i<=18;i++) jc[i]=jc[i-1]*i;
dd(L-1);
for(int i=0;i<=9;i++){
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
for(int j=0;j<=9;j++){
++f[1][(i==j)][gt(j,v[1])];
if(j>0) ++g[1][(i==j)][gt(j,v[1])];
}
for(int j=2;j<=v1;j++){
for(int k=0;k<v1;k++){
for(int p=0;p<=9;p++){
if(p<v[j]){
for(int o=0;o<=2;o++){
f[j][k+(i==p)][0]+=f[j-1][k][o];
if(p>0) g[j][k+(i==p)][0]+=f[j-1][k][o];
}
}
else if(p==v[j]){
for(int o=0;o<=2;o++){
f[j][k+(i==p)][o]+=f[j-1][k][o];
if(p>0) g[j][k+(i==p)][o]+=f[j-1][k][o];
}
}
else{
for(int o=0;o<=2;o++){
f[j][k+(i==p)][2]+=f[j-1][k][o];
if(p>0) g[j][k+(i==p)][2]+=f[j-1][k][o];
}
}
}
}
}
for(int j=1;j<=v1;j++){
for(int k=(j+1)/2;k<=j;k++){
ansL+=g[j][k][0]+g[j][k][1];
if(j<v1) ansL+=g[j][k][2];
}
}
}
for(int o=2;o<v1;o+=2){
for(int i=1;i<=9;i++){
ansL-=jc[o-1]/jc[o/2]/jc[o/2-1];
for(int j=1;j<i;j++){
ansL-=jc[o]/jc[o/2]/jc[o/2];
}
}
}
if(v1%2==0){
for(int o=1;o<v[v1];o++){
for(int j=0;j<=9;j++){
if(j==o) continue;
ansL-=jc[v1-1]/jc[v1/2]/jc[v1/2-1];
}
}
ct=0;
for(wt=0;wt<=9;wt++){
if(v[v1]==wt) continue;
dfs(v[v1],v1,1,L-1);
}
ansL-=ct;
}
dd(R);
for(int i=0;i<=9;i++){
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
for(int j=0;j<=9;j++){
++f[1][(i==j)][gt(j,v[1])];
if(j>0) ++g[1][(i==j)][gt(j,v[1])];
}
for(int j=2;j<=v1;j++){
for(int k=0;k<v1;k++){
for(int p=0;p<=9;p++){
if(p<v[j]){
for(int o=0;o<=2;o++){
f[j][k+(i==p)][0]+=f[j-1][k][o];
if(p>0) g[j][k+(i==p)][0]+=f[j-1][k][o];
}
}
else if(p==v[j]){
for(int o=0;o<=2;o++){
f[j][k+(i==p)][o]+=f[j-1][k][o];
if(p>0) g[j][k+(i==p)][o]+=f[j-1][k][o];
}
}
else{
for(int o=0;o<=2;o++){
f[j][k+(i==p)][2]+=f[j-1][k][o];
if(p>0) g[j][k+(i==p)][2]+=f[j-1][k][o];
}
}
}
}
}
for(int j=1;j<=v1;j++){
for(int k=(j+1)/2;k<=j;k++){
ansR+=g[j][k][0]+g[j][k][1];
if(j<v1) ansR+=g[j][k][2];
}
}
}
for(int o=2;o<v1;o+=2){
for(int i=1;i<=9;i++){
ansR-=jc[o-1]/jc[o/2]/jc[o/2-1];
for(int j=1;j<i;j++){
ansR-=jc[o]/jc[o/2]/jc[o/2];
}
}
}
if(v1%2==0){
for(int o=1;o<v[v1];o++){
for(int j=0;j<=9;j++){
if(j==o) continue;
ansR-=jc[v1-1]/jc[v1/2]/jc[v1/2-1];
}
}
ct=0;
for(wt=0;wt<=9;wt++){
if(v[v1]==wt) continue;
dfs(v[v1],v1,1,R);
}
ansR-=ct;
}
printf("%lld",ansR-ansL);
return 0;
}