在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=0n−1trunc((ax+b)/c)
先保证a,b<c。
不妨令M是(ax+b)/c的最大值。
=
∑
∑
j
=
0
M
(
a
x
+
b
)
/
c
>
=
j
=\sum\sum_{j=0}^{M}(ax+b)/c>=j
=∑∑j=0M(ax+b)/c>=j
=
∑
∑
j
=
0
M
x
>
=
(
c
j
−
b
)
/
a
=\sum\sum_{j=0}^{M}x>=(cj-b)/a
=∑∑j=0Mx>=(cj−b)/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(n−1)−trunc((cj−b−1)/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)
=(n−1)M−∑j=0M−1trunc((c(j+1)−b−1)/a)
=
(
n
−
1
)
M
−
s
u
m
(
c
,
c
−
b
−
1
,
a
,
M
)
=(n-1)M-sum(c,c-b-1,a,M)
=(n−1)M−sum(c,c−b−1,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);
}
}