jzoj6025 【GDOI2019模拟2019.2.16】加农炮 (stern brocot树,类欧)

86 篇文章 0 订阅
31 篇文章 0 订阅

在这里插入图片描述
在这里插入图片描述

在stern brocot上走,任意分母为n的点,其到根路径的拐点最大是O(log n)级别的。(待证)
于是开始二分,判定用类欧。式子这么推
s u m ( a , b , c , n ) sum(a,b,c,n) sum(a,b,c,n)
= ∑ i = 0 n − 1 t r u n c ( ( a x + b ) / c ) =\sum_{i=0}^{n-1}trunc((ax+b)/c) =i=0n1trunc((ax+b)/c)
先保证a,b<c。
不妨令M是(ax+b)/c的最大值。
= ∑ ∑ j = 0 M ( a x + b ) / c &gt; = j =\sum\sum_{j=0}^{M}(ax+b)/c&gt;=j =j=0M(ax+b)/c>=j
= ∑ ∑ j = 0 M x &gt; = ( c j − b ) / a =\sum\sum_{j=0}^{M}x&gt;=(cj-b)/a =j=0Mx>=(cjb)/a
= ∑ j = 1 M ( n − 1 ) − t r u n c ( ( c j − b − 1 ) / a ) =\sum_{j=1}^M(n-1)-trunc((cj-b-1)/a) =j=1M(n1)trunc((cjb1)/a)
= ( n − 1 ) M − ∑ j = 0 M − 1 t r u n c ( ( c ( j + 1 ) − b − 1 ) / a ) =(n-1)M-\sum_{j=0}^{M-1}trunc((c(j+1)-b-1)/a) =(n1)Mj=0M1trunc((c(j+1)b1)/a)
= ( n − 1 ) M − s u m ( c , c − b − 1 , a , M ) =(n-1)M-sum(c,c-b-1,a,M) =(n1)Msum(c,cb1,a,M)
a,c每次换位,所以复杂度是log a + log c的。
递归出口是a=0。
二分的细节可能有一点点多,逻辑是这样的:
我们最终要求的是第一个calc()>=k,也就是左侧点数>=k的直线。
那么,假如当前点即合法,先更新答案。
然后,更优的答案一定在右子树,一直向右走直到变为不合法(注意,沿路的都可以更新答案),更优的答案一定在这个不合法的左子树中。

假如当前点不合法,那么就向左走,走到一个点合法,那更优答案在这个点的右子树。
然后上述过程使用二分进行即可。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
struct frac{
	ll y,x;
	bool operator <(const frac &b)const{
		return x * b.y < y * b.x || x * b.y == y * b.x && (x < b.x || y < b.y);
	}
} L,R,ans,tmp;
ll tot,T,n,m;
ll k;

// sum x=0..n-1, trunc((ax+b)/c)
ll sum(ll a,ll b,ll c,ll n) {
	ll ret=0;
	if (a>=c){
		ret+=a/c*(0+n-1)*n/2;
		a%=c;
	}
	if (b>=c){
		ret+=n*(b/c);
		b%=c;
	}
	if(a==0)return ret;
	ll M=max(b/c,(a*(n-1)+b)/c);
	return ret+(n-1)*M-sum(c,c-b-1,a,M);
}


ll calc(frac e){
	if(e.x*(m-1)/e.y<n)
		return sum(e.x,0,e.y,m);
	ll lim=(n-1)*e.y/e.x+1;
	return sum(e.x,0,e.y,lim) + (m-1-lim+1)*(n-1);
}

void go(frac now){
	if(now.x>=n||now.y>=m)return;
	ll step=-1,csl=0,csr=1e6;
	if (calc(now)<k){
		//left
		if(L.x!=0) csr=(n-1-now.x)/L.x;
		if(L.y!=0) csr=min(csr,(m-1-now.y)/L.y);
		while(csl<=csr){
			int mid=csl+csr>>1;
			tmp=(frac){now.y+L.y*mid,now.x+L.x*mid};
			if(calc(tmp)>=k){
				step=csr=mid;
				csr--;
			} else csl=mid+1;
		}
		if (step>0){
			now.y+=step*L.y,now.x+=step*L.x;
			R=now;R.x-=L.x,R.y-=L.y;
			go(now);
			return;
		}
	} else {
		ans=now;
		//right
		if(R.x!=0) csr=(n-1-now.x)/R.x;
		if(R.y!=0) csr=min(csr,(m-1-now.y)/R.y);
		csr++;
		while(csl<=csr){
			int mid=csl+csr>>1;
			tmp=(frac){now.y+R.y*mid,now.x+R.x*mid};
			if(calc(tmp)<k){
				step=csr=mid;
				csr--;
			} else csl=mid+1;
		}
		if (step>0){
			now.y+=step*R.y,now.x+=step*R.x;
			L=now; L.y-=R.y,L.x-=R.x;
			ans=now;ans.x-=R.x,ans.y-=R.y;
			go(now);
		}
	}
}

int main(){
	freopen("cannon.in","r",stdin);
	freopen("cannon.out","w",stdout);
	for(cin>>T;T;T--){
		scanf("%lld%lld%lld",&n,&m,&k);
		if (k<m){
			printf("%lld %lld\n",1,k+1);
			continue;
		}
		if(k>n*m-1-(n-1)){
			printf("%lld %lld\n",k-(n*m-1-n+1)+1,1);
			continue;
		}
		k-=m-1;
		L.x=R.y=0,L.y=R.x=1;swap(L,R);
		go((frac){1,1});
		ll u=calc(ans),cd=u-k;
		frac z;
		ll rat = min((n-1) / ans.x, (m-1) / ans.y);
		z.x=rat * ans.x;
		z.y=rat * ans.y;
		ans.x=z.x-ans.x*cd;
		ans.y=z.y-ans.y*cd;
		printf("%d %d\n",ans.x+1,ans.y+1);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值