CF1870F Lazy Numbers 题解

发布时间 2024-01-06 08:18:13作者: Pengzt

CF1870F

题意:给一个长度为 \(n\) 的排列,求在其在 \(k\) 进制下按字典序排序后 \(\sum[p_i=i]\) 的值(\(n\le10^{18}\))。

直接做是不好办的,只能在一些数中找到 \(p_i\) 的大小关系。

在手摸的过程中会发现一些长度相等的数之间会插入一些其它长度的数,很麻烦,考虑按长度分类。然后就可以发现 \(k\) 进制表示中长度均为 \(l\) 的数一定有 \(i<j\)\(p_i<p_j\)。考虑从这里下手。

把这个东西看作一个类似于分段函数的东西,\(f_l(i)=p_i\)\(l\) 表示的是长度。其中 \(p_i\) 的求解是简单的,从前往后扫一遍即可。

观察这个函数,发现肯定是递增的,那么在这个函数中符合条件的点一定在 \(x-y=0\) 的直线上,容易发现如果有符合条件的点,一定只有一段区间内的点落在直线上,因为 \(f_l'(i)\) 始终 \(\ge 1\)

但这样并不好统计,显然的,因为 \(x-y=0\) 的斜率为 \(1\),令 \(g_l(i)=f_l(i)-i\),就变为了统计一个不降的序列的值为 \(0\) 的区间的长度。因为不能从前往后扫,二分两次即可。

代码:

typedef long long ll;
ll n,m;
ll pw[65],c;
ll calc(ll a,ll b,ll c){
	if(!b||!c)return 0;
	return (b<=a/c)?(b*c):a;
}
ll check(int len,ll mid){
	ll res=0,tmp=mid;
	for(int i=len;i>=0;i--)res+=tmp-pw[i]+1,tmp/=m;
	if(len==c)return res;
	for(int i=len+1;i<c;i++)res+=pw[i-len]*(mid-pw[len]);
	res+=calc(n+1,pw[c-len],mid)-pw[c];
	return res;
}
void solve(){
	cin>>n>>m;
	pw[c=0]=1;while(pw[c]<=n/m)pw[c+1]=pw[c]*m,c++;
	ll ans=0;
	for(int i=0;i<=c;i++){
		ll l=pw[i],r=((i==c)?n:(pw[i+1]-1)),L=r+1,R=l-1;
		while(l<=r){
			ll mid=(l+r)>>1;
			if(check(i,mid)>=mid)r=mid-1;
			else l=mid+1;
		}
		L=l;l=pw[i],r=((i==c)?n:(pw[i+1]-1));
		while(l<=r){
			ll mid=(l+r)>>1;
			if(check(i,mid)<=mid)l=mid+1;
			else r=mid-1;
		}
		R=r;
		ans+=max(0ll,R-L+1);
	}
	cout<<ans<<"\n";
}
int main(){
	ios::sync_with_stdio(0);cin.tie(0);
	int T;cin>>T;while(T--)solve();
	return 0;
}