Codeforces 121 D. Lucky Segments

問題

lucky numberを、全ての桁が4か7であるような数とする。


n個の区間[ l[i],r[i] ]が与えられる。
一つの区間を、一回の操作で[l[i]+1,r[i]+1]または[l[i]-1,r[i]-1]に移動させることができる。


全体でk回の操作ができるとき、
全ての区間に含まれていて、かつlucky numberであるような数を最大で何個にできるか、求めよ。

制約条件

n≦10^5
l[i],r[i],k≦10^18

方針

lucky numberを全生成して尺取法や二分法を使う解法が思い浮かぶが、
lucky numberは全部で20万個ほどあり、TLEになる。


全部の区間がある区間[L,R]を含むのは、
この区間が一番小さい区間の大きさ以下であり、
全ての区間の左端の数がL以下かつ、右端の値がR以上であることが必要十分である。


全ての区間の左端をL以下にするコストと、右端をR以上にするコストは、独立に求めることができる。
これは尺取っぽくlucky[i]とlucky[i-1]に入っている左端をL以下にするコスト+今までの左端の数*(lucky[i]-lucky[i-1])を更新していくことで求められる。


全ての左端右端をcl[i]以上,cr[i]以下にするコストが前計算で求まったら、
普通の尺取を使って答えを求めればいい。


掛け算がlong longの範囲でオーバーフローするので、
オーバーフローしたらk+1に適宜置き換えるなどの工夫が少し必要。

ソースコード

int n,m;
ll k,l[100000],r[100000];
ll lucky[1000000];
ll cl[1000000],cr[1000000];
ll M(ll a,ll b){
	if(1.0*a*b>k+1)return k+1;
	return a*b;
}
void run()
{
	m=1;
	for(int i=0;lucky[i]*10+7<(ll)1e18;i++){
		lucky[m++]=lucky[i]*10+4;
		lucky[m++]=lucky[i]*10+7;
	}
	cin>>n>>k;
	ll width=(ll)2e18;
	rep(i,n){
		cin>>l[i]>>r[i];
		width=min(width,r[i]-l[i]);
	}
	sort(l,l+n);
	sort(r,r+n);
	
	int j=n-1;
	for(int i=m-2;i>=0;i--){
		cl[i]=cl[i+1]+M(n-j-1,lucky[i+1]-lucky[i]);
		while(j>=0&&lucky[i]<=l[j]){
			cl[i]+=l[j--]-lucky[i];
			cl[i]=min(cl[i],k+1);
		}
	}
	j=0;
	for(int i=1;i<m;i++){
		cr[i]=cr[i-1]+M(j,lucky[i]-lucky[i-1]);
		while(j<n&&lucky[i]>=r[j]){
			cr[i]+=lucky[i]-r[j++];
			cr[i]=min(cr[i],k+1);
		}
	}
	
	j=0;
	int ans=0;
	for(int i=1;i<m;i++){
		j=max(i,j);
		while(j<m&&lucky[j]-lucky[i]<=width&&cl[i]+cr[j]<=k)j++;
		ans=max(ans,j-i);
	}
	cout<<ans<<endl;
}