[HEOI2015]定价

链接:

P4109


题意:

给出一个数荒谬程度的定义:

首先将 \(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;
}

posted @ 2021-08-27 12:06  llmmkk  阅读(45)  评论(0编辑  收藏  举报