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);
}
posted @ 2021-07-01 15:13  oisdoaiu  阅读(65)  评论(0编辑  收藏  举报