[HEOI2015]定价
链接:
题意:
给出一个数荒谬程度的定义:
首先将 \(p\) 看做一个由数字组成的字符串(不带前导 \(0\));
然后,如果 \(p\) 的最后一个字符是 \(0\),就去掉它。重复这一过程,直到 \(p\) 的最后一个字符不是 \(0\);
记 \(p\) 的长度为 \(a\),如果此时 \(p\) 的最后一位是 \(5\),则荒谬程度为 \(2a-1\),否则为 \(2a\)。
有 \(T(T\leq100)\) 次询问,每次询问 \([l,r](1\leq l\leq r\leq 10^9)\) 中荒谬程度最低的数。
分析:
考虑分块,设块长为 \(B\),块两边的数直接暴力算。考虑中间块怎么做。对于一个 \([2000,6000]\) 的询问,我们发现只需要处理 \(a*10^3\) 的这几个数,他们一定比其余的数更优。受到启发我们将块长设成 \(10^k\),此时对于中间块,我们就可以直接除以 \(10^k\) 缩小范围,之后就可以递归求解。
设暴力算数的荒谬程度的时间复杂度为 \(O(p)\)。则时间复杂度 \(O(\log_B{(r-l+1)}*B*p*T)\)。由于我们需要将块长设为 \(10^k\),手玩一下发现设块长为 \(10\) 比较优。
代码:
#include<bits/stdc++.h>
using namespace std;
int T;
inline int p(int n){
if(n==0)return 0x7fffffff;//特判无解
int tn=n,l=0;
while(tn)l++,tn/=10;
while(n%10==0)l--,n/=10;
if(n%10==5)return l*2-1;
return l*2;
}
inline int lbin(int l){return (l+9)/10*10;}
inline int rbin(int r){return r/10*10;}//中间块边界
inline int solve(int l,int r){
if(l>r)return 0;//特判无解
if(l==r)return l;
int tl=lbin(l),tr=rbin(r),tans,tp=0x7fffffff;
for(int i=l,v=p(i);i<min(r,tl);i++,v=p(i))
if(v<tp)tp=v,tans=i;
for(int i=max(l,tr+1),v=p(i);i<=r;i++,v=p(i))
if(v<tp)tp=v,tans=i;
tl/=10,tr/=10;
int temp=solve(tl,tr)*10;
if(p(temp)<tp)tans=temp;
return tans;
}
signed main(){
cin>>T;
while(T--){
int l,r;cin>>l>>r;
cout<<solve(l,r)<<'\n';
}
return 0;
}