题意:
f ( d , x ) f(d,x) f(d,x)表示1-x中d的出现次数。现在给你d,n,让你求最大的x<=n使得 f ( d , x ) = x f(d,x)=x f(d,x)=x
题解:
它可以用两个函数表示:y=x和一个波动函数的交点即是
f
(
d
,
x
)
=
x
f(d,x)=x
f(d,x)=x的解。那么如果
f
(
d
,
n
)
=
n
f(d,n)=n
f(d,n)=n,答案就是n,否则将n-max(1,abs(f(d,n)-n)/17),因为n-1比n最多小18位,然后我们每次至少减1位,如此往复即可得到正解,而n与f(d,n)是同一个数量级的,那么其实要做的次数并不多。
记忆化搜索需要优化,对于当前位置,所有小于等于当前位置值得情况,都有
(
p
o
s
−
1
)
∗
1
0
p
o
s
−
2
(pos-1)*10^{pos-2}
(pos−1)∗10pos−2种情况,这个手模一下即可得到:
1-9有1种可能,1-99有210种可能,1-999有3100种可能。。。
那么对于当前位置是d的情况,分成两种:如果它不是上界,那么有
1
0
p
o
s
−
1
10^{pos-1}
10pos−1种可能,否则就是剩下的数种可能,比如d=1
那么2000的时候1在第4位有1000种可能,但是1005时,只有6种可能。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll p[20],nex[20],a[20];
int d;
ll dfs(int pos)
{
if(pos<=0)
return 0;
ll ans=(ll)(a[pos])*(pos-1)*(pos-2>=0?p[pos-2]:0ll)+(d<a[pos])*p[pos-1];
a[pos]==d&&(ans+=nex[pos-1]+1);
ans+=dfs(pos-1);
return ans;
}
int main()
{
p[0]=1;
for(int i=1;i<=18;i++)
p[i]=p[i-1]*10ll;
int t;
scanf("%d",&t);
while(t--)
{
ll x;
scanf("%d%lld",&d,&x);
ll ret=x;
for(int i=1;i<20;i++)
a[i]=ret%10,nex[i]=nex[i-1]+a[i]*p[i-1],ret/=10;
ll y=dfs(19);
while(x!=y)
{
x-=max((x-y>=0?x-y:y-x)/18,1ll);
ret=x;
for(int i=1;i<20;i++)
a[i]=ret%10,nex[i]=nex[i-1]+a[i]*p[i-1],ret/=10;
y=dfs(19);
}
printf("%lld\n",x);
}
return 0;
}