CF346E 【Doodle Jump】
tag:类欧函数,二分
本蒟蒻考场上的\(nlog^21e9\)的做法(官方正解是\(nlog\)的,不过思路大致相同)
先画一画跳的过程(或者写个程序)比如\(a=7,p=26\)
7 14 21 2 9 16 23 4 11 18 25 6 13 20 1 8 15 22 3 10 17 24 5 12 19
会发现路径一定可以分成几段,每一段都是一个公差为a的等差数列
- 7 14 21
- 2 9 16 23
- 4 11 18 25
- 6 13 20
- 1 8 15 22
- 3 10 17 24
- 5 12 19
然后再仔细一点还能发现首项连起来也是一个等差数列,公差为\(-p\%a\),模数为\(a\)
考虑将位置每\(a\)个分成一组,用\((i/a,i\%a)\)表示位置\(i\)那么跳跃路径就是:
- (0,7)(1,7)(2,7)
- (0,2)(1,2)(2,2)(3,2)
- (0,4)(1,4)(2,4)(3,4)
- (0,6)(1,6)(2,6)
- \(\cdots\)
然后又能看出一些性质:
- 如果一段路径不足\(\frac pa\)个点,就直接忽略(显然只可能是最后一段)
假设这个路径只有\(k\)个点,那么这条路径并不会影响\(k+1\)以后的组内\(dismax\),所以无法缩小\(dismax\),直接忽略
- 第\(\frac pa+1\)段的元素是不用考虑的(这里假设\(an>p\))
最后一个组是不完整的(只有\(p\%a\)个),所以\(dismax\)是小于等于前面的段的\(dismax\)
有了这两个性质,不难发现每一组内的点是一模一样的,所以只用考虑第一组是否满足条件
由于之前发现了首项是一个公差为\(-p\%a\),模\(a\)意义下的等差数列,所以就转化成了一个求的东西相同而规模更小的问题
\(a'=a-p\%a,p'=a\),问题在于\(n'\),即跳跃路径包含几段等差数列
首先如果我们知道包含\(k\)段,那么元素总个数=\(\frac pa\cdot k+\frac{(p\%a)\cdot k}a\),第一项是算大体贡献,第二项是某些段会多出一项
然后二分一下就好了(雾)
也就是说我们完成了\(f(a,p)\to f(a-p\%a,a)\),然而类欧几里得函数应该是\(f(a,p)\to f(p\%a,a)\),从复杂度出发倒回去想的话,把跳跃过程倒着跳回来和原问题是等价的,所以实际上就是\(f(a,p)\to f(min(a-p\%a,p\%a),a)\),复杂度为类欧函数的\(log1e9\),然后我比正解多一个二分的\(log\)
边界情况就是\(a\cdot n< p\),判一下\(h\geq a\)
一些细节
由于我们缩小问题规模和转化为倒着跳的时候,实际上在边界外面是有一个点的,所以还要判\(h\geq p-a\cdot n\)(注意原问题不用判,\(fst\)变量表示是否是原问题)
char solve(int a, int n, int p, int h, char fst){
a %= p;
if(p-a<a) return solve(p-a,n,p,h,false); //倒着跳
if(1ll*n*a<p) if(!fst) return h>=a and h>=p-n*a; else return h>=a;
int A = p%a, head=1, tail=p;
while(head<tail){
int mid = head+tail >> 1;
ll cnt = 1ll*(p/a)*mid+(1ll*A*(mid-1))/a;
if(cnt<=n) head = mid+1;
else tail = mid;
}
return solve(a-p%a,head-2,a,h,false);
}