Codeforces 70 C. Lucky Tickets

問題

切符には二つの番号(x,y)がついている。
切符がluckyであるとは、x*y=rev(x)*rev(y)が成り立つことをいう。


ただし、rev(x)は、xの数字を反転させたもので、
例えばrev(132)=231, rev(1200)=21である。


今、xとyをx≦maxx,y≦maxyとなるように決め、
(p,q) 1≦p≦xかつ1≦q≦yとなる切符を全て発行する。
このなかにluckyである切符をw個以上にしたい。


このとき、x*yの最小値およびそのときのx,yを求めよ。
x*yが最小値をとるようなx,yが複数あるときはどれを出力してもかまわない。

制約条件

maxx,maxy≦10^5

方針

luckyな切符を全列挙して、しゃくとり的なことをする。
yをxの関数と見たとき、xを増やすとyは単調減少する。


したがってx=1からmaxxまで、必要なyをしゃくとりっぽく計算することができる。


luckyな切符は次のようにして全列挙できる。
x*y=rev(x)*rev(y)
⇔x/rev(x)=rev(y)/y
したがって、xを決めたときにluckyになるyは、
rev(y)/yがx/rev(x)に等しくなるyである。


これは有理数クラスRを作って、p/rev(p)および、そのときのpの値をmultisetに入れることで、二分探索を使って全て列挙できる。


xを決めたとき、
今のところ決まっているy以下で、luckyな切符を全て作る。
luckyな切符の数がwより大きい間、yを減らし続ける。
これにより現在のxに対応するyが求まる。


それで答えを更新していけばいい。

ソースコード

const ll INF=1ll<<60;

struct R{
	int n,d;
	bool operator<(const R &r)const{
		return (ll)n*r.d<(ll)r.n*d;
	}
	bool operator==(const R &r)const{
		return n==r.n&&d==r.d;
	}
	R(int n,int d){
		int g=__gcd(n,d);
		this->n=n/g; this->d=d/g;
	}
	R(){}
};
multimap<R,int> p_revp;
int maxx,maxy,w;

int rev(int x){
	int d[20],n=0;
	for(;x;x/=10)d[n++]=x%10;
	rep(i,n)x*=10,x+=d[i];
	return x;
}
void run()
{
	cin>>maxx>>maxy>>w;
	p_revp.clear();
	for(int i=1;i<=max(maxx,maxy);i++)p_revp.insert(mp(R(i,rev(i)),i));
	
	ll ans=INF;
	int ansx=maxx,ansy=maxy,cnt=0;
	map<int,int> ys;
	
	for(int x=1;x<=maxx;x++){
		multimap<R,int>::iterator it=p_revp.find(R(rev(x),x));
		
		while(it!=p_revp.end()&&it->first==R(rev(x),x)){
			if(it->second<=ansy)cnt++, ys[it->second]++;
			it++;
		}
		
		while(cnt>w){
			map<int,int>::iterator it=--ys.end();
			int t=min(it->second,cnt-w);
			cnt-=t;
			it->second-=t;
			if(it->second==0)ys.erase(it);
			it--;
		}
		/*
		dbg(x); dbg(w); dbg(cnt);
		fr(i,ys)cerr<<" "<<i->first<<" "<<i->second<<endl;
		*/
		if(cnt>=w){
			map<int,int>::iterator it=--ys.end();
			int y=it->first;
			if(ans>x*(ll)y){
				ansx=x; ansy=y;
				ans=(ll)x*y;
			}
		}
	}
	if(ans!=INF)cout<<ansx<<" "<<ansy<<endl;
	else cout<<-1<<endl;
}