zoukankan      html  css  js  c++  java
  • 【做题】agc003E

    题意:有一个序列,初始是从(1)(n)(n)个数。有(q)次操作,每次操作给出(q_i),把当前的序列重复无数遍,然后截取最前面的(q_i)个元素作为新序列。要求输出完成所有(q)次操作后,每个(1)(n)的数各出现了多少次。

    (n,q leq 10^5, \, q_i leq 10^{18})

    首先一个要点在于操作后序列的长度可以达到(10^{18})。因此,要从每次操作间寻找思路。

    首先,我们发现如果一次操作之后又(q_i)比它小的操作,那么它就是没有意义的。所以,我们可以让(q_i)单调递增。然后,考虑每一次操作后的结果,它就是前一次操作后的结果重复若干遍,再加上前一次操作的一个前缀。前面一部分可以通过计算前一次操作后的结果时乘上一个系数来处理。那么,问题就变成如何后面的不完整部分了。

    更确切地说,我们的答案还要加上一次操作后结果的一个前缀,其长度是(q_i mod p_{i-1})。这里敏感的人或许已经发现了,我们又一个经典的结论,就是一个数对一个不大于它的数取模后,至少减小一倍。证明显然。那么,我们每次二分小一个不大于它的(q_i),最多做(O(log n))次后就能把这个长度缩小到(n)以内。到最后,就是答案序列的一个前缀加了。这里的复杂度是(O(log^2 n))

    因此,我们就能在(O(n log^2 n))时间内解决本题。

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N = 100010;
    int q,n,len[N],val[N],ans[N],m;
    void doit(int x,int v) {
      if (!x) return;
      int t = upper_bound(len+1,len+m+1,x) - len - 1;
      if (t == 0) ans[1] += v, ans[x+1] -= v;
      else {
        val[t] += (x / len[t]) * v;
        doit(x % len[t],v);
      }
    }
    signed main() {
      int x;
      scanf("%lld%lld",&n,&q);
      len[++m] = n;
      for (int i = 1 ; i <= q ; ++ i) {
        scanf("%lld",&x);
        while (m && len[m] >= x) -- m;
        len[++m] = x;
      }
      val[m] = 1;
      for (int i = m ; i >= 2 ; -- i) {
        val[i-1] += val[i] * (len[i] / len[i-1]);
        doit(len[i] % len[i-1],val[i]);
      }
      ans[1] += val[1];
      ans[len[1]+1] -= val[1];
      for (int i = 2 ; i <= n ; ++ i)
        ans[i] += ans[i-1];
      for (int i = 1 ; i <= n ; ++ i)
        printf("%lld
    ",ans[i]);
      return 0;
    }
    

    小结:还是不够熟练。以及,想题时用笔整理思路,或许是个好习惯。

  • 相关阅读:
    windows中dos命令指南
    HDU 2084 数塔 (dp)
    HDU 1176 免费馅饼 (dp)
    HDU 1004 Let the Balloon Rise (map)
    变态杀人狂 (数学)
    HDU 2717 Catch That Cow (深搜)
    HDU 1234 开门人和关门人 (模拟)
    HDU 1070 Milk (模拟)
    HDU 1175 连连看 (深搜+剪枝)
    HDU 1159 Common Subsequence (dp)
  • 原文地址:https://www.cnblogs.com/cly-none/p/9671921.html
Copyright © 2011-2022 走看看