省队集训Ⅱ-Day8

Posted Wild Donkey

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了省队集训Ⅱ-Day8相关的知识,希望对你有一定的参考价值。

Day8

Color

这道题的优化是真的爽, 考场上的代码我存了 \\(6\\) 个版本, 每个版本都有大改, 有时候能优化掉一个复杂度, 而且越快的版本貌似码量越少.

开始的开始, 写了二维的 DP, \\(f_{i, j}\\) 表示第 \\(i\\) 个格子是第 \\(j\\) 个连续的白格子的方案数, 结果发现转移非常麻烦, 于是就弃了.

color-40\'.cpp

然后考虑用 \\(f_i\\) 表示第 \\(i\\) 个格子涂黑, \\(i + 1\\)\\(n\\) 不涂黑的方案数, 写出方程:

\\[f_{i} = \\sum_{j}^{j + k \\leq i} f_{j} \\]

然后发现如果要得到正确的答案, \\(j\\) 可能会出现负数, 因为 \\(f_{i \\in [1, k]}\\) 的答案要从 \\(f_{j\\in[1 - k, 0]}\\) 转移过来. 为了防止 RE, 只能把坐标向右平移 \\(k\\).

对于每个 \\(k\\) 都会进行一次 \\(O(n^2)\\) 的 DP, 将询问离线, 按 \\(k\\) 排序, \\(O(nlogn)\\), 得到了 \\(f\\) 数组.

对于点 \\(x\\) 是不涂色的, 分两部分考虑, 枚举它左边的最近的黑点 \\(i\\), 右边最近的黑点 \\(j\\). 对于每对 \\(j - i \\geq k\\) 的点, 答案就是 \\(f_if_{n - j + 1}\\), 不要忘了左边不涂, 右边不涂和左右都不涂的情况.

写了非常麻烦的版本, 标称 \\(40\'\\), 实测 \\(20\'\\), 因为有一半的点 WA 掉了, 应该是因为我的思路太繁杂, 有一点细节没有处理好.

总复杂度 \\(O(nlogn + n^2(k + m))\\)

unsigned n, m, Ans(0), f[1005];
char flg(0);
struct Que {
  unsigned Quex, Quek, Num, Ans;
  inline const char operator<(const Que &x) const{
    return this->Quek < x.Quek;
  }
}Q[100005];
int main() {
  n = RD(), m = RD();
  for (register unsigned i(1); i <= m; ++i) {
    Q[i].Quex = RD(), Q[i].Quek = RD(), Q[i].Num = i;
  }
  sort(Q + 1, Q + m + 1);
  for (register unsigned T(1); T <= m; ++T) {
    if(Q[T].Quek ^ Q[T - 1].Quek){
      memset(f, 0, sizeof(f));
        f[1] = 1;
      for (register unsigned i(Q[T].Quek); i <= n + Q[T].Quek; ++i) {
        for (register unsigned j(1); j <= i - Q[T].Quek; ++j) {
          f[i] += f[j];
          if(f[i] >= MOD) f[i] -= MOD;
        }
      }
    }
    register unsigned long long TmpA(1);
    for (register unsigned i(1 + Q[T].Quek); i <= n + Q[T].Quek - Q[T].Quex; ++i) {
      TmpA += f[i];
      if(TmpA >= MOD) TmpA -= MOD;
    }
    for (register unsigned i(1 + Q[T].Quek); i < Q[T].Quex + Q[T].Quek; ++i) {
      TmpA += f[i];
      if(TmpA >= MOD) TmpA -= MOD;
    }
    for (register unsigned i(1 + Q[T].Quek); i < Q[T].Quex + Q[T].Quek; ++i) {
      for (register unsigned j(1 + Q[T].Quek); j <= min(n + Q[T].Quek - Q[T].Quex, n + Q[T].Quek + 1 - i); ++j) {
        TmpA = (TmpA + (unsigned long long)f[i] * f[j]) % MOD;
      }
    }
    Q[Q[T].Num].Ans = TmpA;
  }
  for (register unsigned i(1); i <= m; ++i) {
    printf("%u\\n", Q[i].Ans);
  }
  return 0;
}

color-50\'Ex.cpp

这个版本已经能过 40\' 了, 显然我对我的时间复杂度没有准确的估计.

这个版本的更新是设 \\(Sum_i = \\displaystyle{\\sum_{j = 1}^{j < i}}\\), 用前缀和优化将每次求 \\(f\\) 的 DP 降到了 \\(O(n)\\), \\(x\\) 的左右不选情况降到了 \\(O(1)\\), 单次询问降到了 \\(O(n)\\), 所以时间复杂度来到了 \\(O(nlogn + n(k + m))\\). 这是里程碑式的飞跃.

unsigned n, m, Ans(0), f[100005], Sum[100005];
char flg(0);
struct Que {
  unsigned Quex, Quek, Num, Ans;
  inline const char operator<(const Que &x) const{
    return (this->Quek ^ x.Quek) ? (this->Quek < x.Quek) : (this->Quex < x.Quex);
  }
}Q[100005];
int main() {
  n = RD(), m = RD();
  for (register unsigned i(1); i <= m; ++i) {
    Q[i].Quex = RD(), Q[i].Quek = RD(), Q[i].Num = i;
  }
  sort(Q + 1, Q + m + 1);
  for (register unsigned T(1); T <= m; ++T) {
    if(Q[T].Quek ^ Q[T - 1].Quek){
      memset(f, 0, sizeof(f)), f[1] = 1, Sum[0] = 0;
      for (register unsigned i(Q[T].Quek), j(1), TmpFj(0); i <= n + Q[T].Quek; ++i) {
        while (j <= i - Q[T].Quek) {TmpFj += f[j], ++j; if(TmpFj >= MOD) TmpFj -= MOD;}
        f[i] += TmpFj; if(f[i] >= MOD) f[i] -= MOD;
      }
      for (register unsigned i(1); i <= n + Q[T].Quek; ++i) {Sum[i] = Sum[i - 1] + f[i]; if(Sum[i] >= MOD) Sum[i] -= MOD;}
    }
    register unsigned long long TmpA(1);//都不选
    TmpA += Sum[n + Q[T].Quek - Q[T].Quex] - 1;
    if(TmpA >= MOD) TmpA -= MOD;
    TmpA += Sum[Q[T].Quex + Q[T].Quek - 1] - 1;
    if(TmpA >= MOD) TmpA -= MOD;
    for (register unsigned i(Q[T].Quex + Q[T].Quek - 1), TmpFj(0), j(1 + Q[T].Quek); i > Q[T].Quek; --i) {
      while (j <= min(n + Q[T].Quek - Q[T].Quex, n + Q[T].Quek + 1 - i)) {TmpFj += f[j],++j; if(TmpFj >= MOD) TmpFj -= MOD;}
      TmpA = (TmpA + (unsigned long long)f[i] * TmpFj) % MOD;
    }
    Q[Q[T].Num].Ans = TmpA;
  }
  for (register unsigned i(1); i <= m; ++i) {
    printf("%u\\n", Q[i].Ans);
  }
  return 0;
}

color-50\'Ex_Pro.cpp

很遗憾, 这个版本还是 \\(40\'\\).

重新审视我们的转移方程:

\\[f_{i} = \\sum_{j = 1}^{j + k \\leq i} f_{j} \\]

我们发现, 因为 \\(f_{1 - k} = 1\\), \\(f_{i \\in (1 - k, 0]} = 0\\). 那么对于 \\(i <= k\\), 对它做出贡献的值只有 \\(f_{1 - k} = 1\\) (下标是平移之前的, 可能有负数, 平移后不会越界). 这样就可以改一下方程, 让数组下标从 \\(1\\) 开始, 无需平移.

\\[f_{i} = 1 + \\sum_{j = 1}^{j \\leq \\max(i - k, 1)} f_{j} \\]

方程的正确性是这样保证的: 原来的 \\(f_{1 - k} = 1\\) 所转移的情况是除了 \\(i\\) 点涂黑, 其它的点都留白的情况. 对于 \\(f_{i\\in[1, n]}\\), 显然这种情况都存在, 所以就拿出来当一个边界条件讨论, 避免了更多的转移.

所以这次更新是对代码难度的更新, 之前总是算下标恶心死人.

unsigned n, m, Ans(0), f[100005], Sum[100005];
char flg(0);
struct Que {
  unsigned Quex, Quek, Num, Ans;
  inline const char operator<(const Que &x) const{
    return (this->Quek ^ x.Quek) ? (this->Quek > x.Quek) : (this->Quex < x.Quex);
  }
}Q[100005];
int main() {
  n = RD(), m = RD();
  for (register unsigned i(1); i <= m; ++i) {
    Q[i].Quex = RD(), Q[i].Quek = RD(), Q[i].Num = i;
  }
  sort(Q + 1, Q + m + 1);
  for (register unsigned T(1); T <= m; ++T) {
    if(Q[T].Quek ^ Q[T - 1].Quek){
      memset(f, 0, sizeof(f)), Sum[0] = 0;
      for (register unsigned i(1), j(1), TmpFj(1); i <= n; ++i) {
        while (j + Q[T].Quek <= i) {TmpFj += f[j], ++j; if(TmpFj >= MOD) TmpFj -= MOD;}
        f[i] = TmpFj;
      }
      for (register unsigned i(1); i <= n; ++i) {Sum[i] = Sum[i - 1] + f[i]; if(Sum[i] >= MOD) Sum[i] -= MOD;}
    }
    register unsigned long long TmpA(1);//都不选
    TmpA += Sum[n - Q[T].Quex];//不选左 
    if(TmpA >= MOD) TmpA -= MOD;
    TmpA += Sum[Q[T].Quex - 1];//不选右 
    if(TmpA >= MOD) TmpA -= MOD;
    for (register unsigned i(Q[T].Quex - 1), j(1), AddDown; i; --i) {
      AddDown = n - max(Q[T].Quex, i + Q[T].Quek - 1);
      if(AddDown > 0x3f3f3f3f) {AddDown = 0;}
      TmpA = (TmpA + (unsigned long long)f[i] * Sum[AddDown]) % MOD;
    }
    Q[Q[T].Num].Ans = TmpA;
  }
  for (register unsigned i(1); i <= m; ++i) printf("%u\\n", Q[i].Ans);
  return 0;
}

color-50\'_Pro_Max.cpp

这个版本无愧它 Pro Max 的称号, 得了 \\(60\'\\), 这是因为对求答案时的部分进行了剪枝, 因为有时候, 对于 \\(i + k <= x\\), \\(j\\) 可以随便选, 不用枚举, 可以前缀和优化掉, 对于 \\(x > k\\) 的情况, \\(i \\in [1, x - k]}\\) 也可以随便选, 可以优化, 所以询问复杂度变成 \\(O(n - 2k)\\), 相当于优化了很大的常数.

unsigned n, m, Ans(0), f[100005], Sum[100005];
char flg(0);
struct Que {
  unsigned Quex, Quek, Num, Ans;
  inline const char operator<(const Que &x) const{
    return (this->Quek ^ x.Quek) ? (this->Quek < x.Quek) : (this->Quex < x.Quex);
  }
}Q[100005];
int main() {
  n = RD(), m = RD();
  for (register unsigned i(1); i <= m; ++i) {
    Q[i].Quex = RD(), Q[i].Quek = RD(), Q[i].Num = i;
  }
  sort(Q + 1, Q + m + 1);
  for (register unsigned T(1); T <= m; ++T) {
    if(Q[T].Quek ^ Q[T - 1].Quek){
      memset(f, 0, sizeof(f)), Sum[0] = 0;
      for (register unsigned i(1), j(1), TmpFj(1); i <= n; ++i) {
        while (j + Q[T].Quek <= i) {TmpFj += f[j], ++j; if(TmpFj >= MOD) TmpFj -= MOD;}
        f[i] = TmpFj;
      }
      for (register unsigned i(1); i <= n; ++i) {Sum[i] = Sum[i - 1] + f[i]; if(Sum[i] >= MOD) Sum[i] -= MOD;}
    }
    register unsigned long long TmpA(1);//都不选
    TmpA += Sum[n - Q[T].Quex];//不选左 
    if(TmpA >= MOD) TmpA -= MOD;
    TmpA += Sum[Q[T].Quex - 1];//不选右 
    if(TmpA >= MOD) TmpA -= MOD;
    if(Q[T].Quex + 1 > Q[T].Quek) {
      if(Q[T].Quek < 2) TmpA = (TmpA + (unsigned long long)Sum[Q[T].Quex - 1] * Sum[n - Q[T].Quex]) % MOD;
      else {
        TmpA = (TmpA + (unsigned long long)(Sum[Q[T].Quex - Q[T].Quek + 1]) * Sum[n - Q[T].Quex]) % MOD;
        for (register unsigned i(Q[T].Quex - 1), AddDown; i > Q[T].Quex - Q[T].Quek + 1; --i) {
          AddDown = n + 1 - i - Q[T].Quek;
          if(AddDown > 0x3f3f3f3f) AddDown = 0;
          TmpA = (TmpA + (unsigned long long)f[i] * Sum[AddDown]) % MOD;
        }
      }
    } else {
      for (register unsigned i(Q[T].Quex - 1), AddDown; i; --i) {
        AddDown = n - max(Q[T].Quex, i + Q[T].Quek - 1);
        if(AddDown > 0x3f3f3f3f) AddDown = 0;
        TmpA = (TmpA + (unsigned long long)f[i] * Sum[AddDown]) % MOD;
      }
    }
    Q[Q[T].Num].Ans = TmpA;
  }
  for (register unsigned i(1); i <= m; ++i) printf("%u\\n", Q[i].Ans);
  return 0;
}

color-70\'_Pro_Max_Ti.cpp

这个 70\' 非常的丢人, 因为它只有 \\(60\'\\).

优化力度不大, 代码方面减少了讨论, 将两种情况合为一种, 因为两种唯一的区别是枚举 \\(i\\) 的上界. 时间方面只是剪枝了一下, 利用了求答案时利用下标 \\(AddDown\\) 的单调性, 实现了 \\(AddDown = 0\\) 的情况自动跳过, 也体现在 \\(i\\) 的枚举上界上, 询问的复杂度变得十分玄学 \\(O(k)\\) or \\(O(x)\\) or \\(O(n - x)\\) or \\(O(n - k)\\)... 但是它短呀!

unsigned n, m, Ans(0), f[100005], Sum[100005];
char flg(0);
struct Que {
  unsigned Quex, Quek, Num, Ans;
  inline const char operator<(const Que &x) const{
    return (this->Quek ^ x.Quek) ? (this->Quek < x.Quek) : (this->Quex < x.Quex);
  }
}Q[100005];
int main() {
  n = RD(), m = RD();
  for (register unsigned i(1); i <= m; ++i) Q[i].Quex = RD(), Q[i].Quek = RD(), Q[i].Num = i;
  sort(Q + 1, Q + m + 1);
  for (register unsigned T(1); T <= m; ++T) {
    if(Q[T].Quek ^ Q[T - 1].Quek){
      memset(f, 0, sizeof(f)), Sum[0] = 0;
      for (register unsigned i(1), j(1), TmpFj(1); i <= n; ++i) {
        while (j + Q[T].Quek <= i) {TmpFj += f[j], ++j; if(TmpFj >= MOD) TmpFj -= MOD;}
        f[i] = TmpFj;
      }
      for (register unsigned i(1); i <= n; ++i) {Sum[i] = Sum[i - 1] + f[i]; if(Sum[i] >= MOD) Sum[i] -= MOD;}
    }
    register unsigned long long TmpA(1);//都不选
    register unsigned Flor(min(Q[T].Quex - 1, n + 1 - Q[T].Quek)), Ceil(0);
    TmpA += Sum[n - Q[T].Quex];//不选左 
    if(TmpA >= MOD) TmpA -= MOD;
    TmpA += Sum[Q[T].Quex - 1];//不选右 
    if(TmpA >= MOD) TmpA -= MOD;
    if(Q[T].Quex + 1 > Q[T].Quek)
      Ceil = Q[T].Quex - Q[T].Quek + 1, TmpA = (TmpA + (unsigned long long)Sum[Q[T].Quex - max(Q[T].Quek - 1, _1)] * Sum[n - Q[T].Quex]) % MOD;
    for (register unsigned i(Flor), AddDown(n + 1 - i - Q[T].Quek); i > Ceil; --i, ++AddDown) TmpA = (TmpA + (unsigned long long)f[i] * Sum[AddDown]) % MOD;
    Q[Q[T].Num].Ans = TmpA;
  }
  for (register unsigned i(1); i <= m; ++i) printf("%u\\n", Q[i].Ans);
  return 0;
}

color-70\'_Pro_Max_Ti_X.cpp

重新审视这个询问的过程, 发现所有的方案数 \\(Sum_i + 1\\), 减去 \\(x\\) 涂黑的方案数 \\(f_x f_{n - x + 1}\\), 就是不涂 \\(x\\) 的方案数.

所以变成了 \\(O(1)\\) 询问. 这是复杂度的巨大飞跃, 也是代码的进一步缩减, 但是更遗憾的是, 它还是只有 \\(60\'\\)

unsigned n, m, Ans(0), f[100005], Sum[100005];
char flg(0);
struct Que {
  unsigned Quex, Quek, Num, Ans;
  inline const char operator<(const Que &x) const{
    return (this->Quek ^ x.Quek) ? (this->Quek < x.Quek) : (this->Quex < x.Quex);
  }
}Q[100005];
int main() {
  n = RD(), m = RD();
  for (register unsigned i(1); i <= m; ++i)
    Q[i].Quex = RD(), Q[i].Quek = RD(), Q[i].Num = i;
  sort(Q + 1, Q + m + 1);
  for (register unsigned T(1); T <= m; ++T) {
    if(Q[T].Quek ^ Q[T - 1].Quek){
      memset(f, 0, sizeof(f)), Sum[0] = 0;
      for (register unsigned i(1), j(1), TmpFj(1); i <= n; ++i) {
        while (j + Q[T].Quek <= i) {TmpFj += f[j], ++j; if(TmpFj >= MOD) TmpFj -= MOD;}
        f[i] = TmpFj;
      }
      for (register unsigned i(1); i <= n; ++i) {Sum[i] = Sum[i - 1] + f[i]; if(Sum[i] >= MOD) Sum[i] -= MOD;}
    }
    Q[Q[T].Num].Ans = (MOD + (Sum[n] + 1) - ((unsigned long long)f[Q[T].Quex] * f[n - Q[T].Quex + 1] % MOD));
    if(Q[Q[T].Num].Ans >= MOD) Q[Q[T].Num].Ans -= MOD;
  }
  for (register unsigned i(1); i <= m; ++i) printf("%u\\n", Q[i].Ans);
  return 0;
}

color-70\'_Pro_Max_Ti_X_S.cpp

这个 70\' 实测得了 \\(80\'\\). (No -O2) 也是全场唯一的 \\(80\'\\) (为什么我总是得这么奇怪的分啊?)

这是我考试时最后一个版本, 这份代码主要是对 DP 的过程进行剪枝, 因为打表可得, 对于某个 \\(k\\), \\(f_{i \\in [1, k]}\\) 等于 \\(1\\). 又因为排序后 \\(k\\) 单增, 所以我们自然不用每次再求一遍 \\(f_{i \\in [1, k]}\\) 了, 复杂度变成 \\(O(n - k)\\), 对于 \\(k\\) 大的情况下理论上能将整个程序复杂度优化 \\(50\\%\\) 左右. 所以总复杂度 \\(O(nlogn + \\frac {nk}2 + m)\\).

unsigned n, m, Ans(0), f[100005], Sum[100005];
char flg(0);
struct Que {
  unsigned Quex, Quek, Num, Ans;
  inline const char operator<(const Que &x) const{
    return (this->Quek ^ x.Quek) ? (this->Quek < x.Quek) : (this->Quex < x.Quex);
  }
}Q[100005];
int main() {
  n = RD(), m = RD();
  for (register unsigned i(1); i <= m; ++i)
    Q[i].Quex = RD(), Q[i].Quek = RD(), Q[i].Num = i;
  sort(Q + 1, Q + m + 1);
  for (register unsigned T(1); T <= m; ++T) {
    if(Q[T].Quek ^ Q[T - 1].Quek){
      for (register unsigned i(Q[T - 1].Quek + 1); i <= Q[T].Quek; ++i) f[i] = 1, Sum[i] = i;
      for (register unsigned i(Q[T].Quek + 1); i <= n; ++i) {
        f[i] = Sum[i - Q[T].Quek] + 1;
        Sum[i] = Sum[i - 1] + f[i]; if(Sum[i] >= MOD) Sum[i] -= MOD;
      }
    }
    Q[Q[T].Num].Ans = (MOD + (Sum[n] + 1) - ((unsigned long long)f[Q[T].Quex] * f[n - Q[T].Quex + 1] % MOD));
    if(Q[Q[T].Num].Ans >= MOD) Q[Q[T].Num].Ans -= MOD;
  }
  for (register unsigned i(1); i <= m; ++i) printf("%u\\n", Q[i].Ans);
  return 0;
}

color-100\'Ex_Pro_Max_Ti_X_Ultra_Extreme.cpp

在写正解之前, 先来看一下以张业琛为代表的选手的 \\(O(\\frac{nm}{k})\\) 做法. 我一听也很疑惑, 我的算法 \\(k\\) 在分子上, 为什么你的 \\(k\\) 在分子上?

我们知道一个黑点前面一定有 \\(k - 1\\) 个连续的白点, 所以我们可以把这些白点和对应的黑点绑到一起, 成为一个长度为 \\(k\\) 的段, 值得一提的是, 由于第一个黑点前面不一定有 \\(k - 1\\) 个白点, 所以可以在格子 \\(1\\) 前面加 \\(k - 1\\) 的格子, 可以作为容纳第一个黑点对应白点的位置, 这样我们就有最多 \\(\\lfloor \\frac{n + k - 1}{k} \\rfloor = \\lceil \\frac nk \\rceil\\) 个黑点了.

考虑有 \\(i\\) 个点, 那么它们占用的长度是 \\(ki\\), 则还剩下 \\(n + k - 1 - ki\\) 个自由的, 没有约束的白点, 像放哪放哪. 这时, 把黑点加上它们对应的尾巴看成一个点, 和自由白点加在一起做组合数即可求出方案数. 点数是 \\(n + k - 1 - ki + i\\), 从这些点中选 \\(i\\) 个点定为带尾巴的黑点, 剩下的是自由白点的方案数是 \\(\\binom{n + k - 1 - ki + i}{i}\\).

对于 \\(i \\in [1, \\lfloor \\frac{n + k - 1}{k} \\rfloor]\\)\\(\\binom{n + k - 1 - ki + i}{i}\\), 就能得到对于 \\(n\\) 长度和 \\(k\\) 间隔没有约束条件的合法涂色方案数.

接下来考虑 \\(x\\) 点为白的情况, 仍然是前面的想法, 即所有的情况减去 \\(x\\) 为黑的情况.

\\(x\\) 为黑的情况, 我们知道 \\(i \\in [x - k + 1, x - 1] \\cup [x + 1, x + k - 1]\\) 必须是白点. 所以 \\(x\\) 右边最多有 \\(\\lfloor \\frac{n - x}{k} \\rfloor\\), 左边最多有 \\(\\lfloor \\frac{x - 1 + k - 1 - k + 1}{k} \\rfloor = \\lfloor \\frac{x - 1}{k} \\rfloor\\). 对于 \\(i > x\\) 由于黑点的尾巴在左边, 所以带尾巴的黑点可以贴脸放, 而且不需要像求整段一样在左端加上 \\(k - 1\\) 个点, 则 \\(x\\) 右边放 \\(i\\) 个黑点的合法方案数为 \\(\\binom {n - x - ki + i}{i}\\), 则 \\(x\\) 的左边放 \\(i\\) 个的合法方案数为 \\(\\binom {x - 1 - ki + i}{i}\\).

也就是说, 对于询问 \\(x\\), \\(k\\), 答案是:

\\[\\Bigg(\\sum_{i = 1}^{i \\leq \\lfloor \\frac{n + k - 1}{k} \\rfloor}\\dbinom {n + k - 1 - ki + i}{i}\\Bigg) - \\Bigg(\\sum_{i = 1}^{i \\leq \\lfloor \\frac{n - x}{k} \\rfloor}\\dbinom {n - x - ki + i}{i}\\Bigg) * \\Bigg(\\sum_{i = 1}^{i \\leq \\lfloor \\frac{x - 1}{k} \\rfloor}\\dbinom {x - 1 - ki + i}{i}\\Bigg) \\]

只要可以 \\(O(1)\\) 求二项式系数, 我们就可以 \\(O(\\frac{n}{k})\\) 回答询问了.

众所周知, 二项式系数:

\\[\\binom {a}{b} = \\frac{a!}{b!(a - b)!} \\]

我们只要预处理出 \\(i!\\)\\(inv_{i!}\\) 即可, \\(O(n)\\) 求阶乘没问题, 主要问题是逆元.

要预处理 \\(inv_{i!}, i \\in [1, n]\\), 我们需要先用快速幂求出 \\(inv_{n!}\\).

假设 \\(inv_{i!}\\) 已经求出, 要求 \\(inv_{(i - 1)!}\\) 使得 \\((i - 1)!inv_{(i - 1)!} \\equiv 1(\\bmod p)\\).

\\(i!inv_{(i - 1)!} \\equiv i(\\bmod p)\\), 又因为 \\(i!inv_{i!} \\equiv 1(\\bmod p)\\), 所以 \\(inv_{(i - 1)!} \\equiv inv_{i!} * i (\\bmod p)\\).

这样, 总的复杂度就是 \\(O(\\frac {nm}k)\\) 了.

接下来考虑正解.

我们现在有算法可以做到 \\(O(nlogn + \\frac {nk}2 + m)\\), 也有算法能做到 \\(O(\\frac {nm}k)\\). 所以可以数据分治, 对于 \\(k \\leq \\sqrt n\\) 的询问, 用前一种算法, 对于 \\(k > \\sqrt n\\) 的做法, 用后一种做法, 这样, 最坏的复杂度是 \\(O(nlogn + (n + m) \\sqrt n)\\).

最后是代码:

unsigned n, m, Ans(0), f[100005], Sum[100005], T(1), Sq;
unsigned long long Inv[200005], Fac[200005];
char flg(0);
struct Que {
  unsigned Quex, Quek, Num, Ans;
  inline const char operator<(const Que &x) const{
    return (this->Quek ^ x.Quek) ? (this->Quek < x.Quek) : (this->Quex < x.Quex);
  }
}Q[100005];
int main() {
  n = RD(), m = RD(), Sq = sqrt(n);
  for (register unsigned i(1); i <= m; ++i) Q[i].Quex = RD(), Q[i].Quek = RD(), Q[i].Num = i;
  sort(Q + 1, Q + m + 1), Fac[0] = 1;
  for (register unsigned i(1); i <= n + Q[m].Quek; ++i) Fac[i] = Fac[i - 1] * i % MOD;
  register unsigned TmpM(MOD - 2);
  register unsigned long long TmpFac(Fac[n + Q[m].Quek]);
  Inv[n + Q[m].Quek] = 1;
  while (TmpM) {
    if(TmpM & 1) Inv[n + Q[m].Quek] = Inv[n + Q[m].Quek] * TmpFac % MOD;
    TmpFac = TmpFac * TmpFac % MOD, TmpM >>= 1;}
  for (register unsigned i(n + Q[m].Quek - 1); i < 0x3f3f3f3f; --i) Inv[i] = Inv[i + 1] * (i + 1) % MOD;
  for (; Q[T].Quek <= Sq && T <= m; ++T) {
    if(Q[T].Quek ^ Q[T - 1].Quek){
      for (register unsigned i(Q[T - 1].Quek + 1); i <= Q[T].Quek; ++i) f[i] = 1, Sum[i] = i;
      for (register unsigned i(Q[T].Quek + 1); i <= n; ++i){
        f[i] = Sum[i - Q[T].Quek] + 1, Sum[i] = Sum[i - 1] + f[i]; if(Sum[i] >= MOD) Sum[i] -= MOD;}
    }
    Q[Q[T].Num].Ans = (MOD + (Sum[n] + 1) - ((unsigned long long)f[Q[T].Quex] * f[n - Q[T].Quex + 1] % MOD));
    if(Q[Q[T].Num].Ans >= MOD) Q[Q[T].Num].Ans -= MOD;
  }
  for (register unsigned long long TmpTotal(1), TmpLe(1), TmpRi(1); T <= m; ++T) {
    for (register unsigned i((n + Q[T].Quek - 1)/ Q[T].Quek), Len(n + Q[T].Quek - 1 - Q[T].Quek * i + i); i; --i, Len += (Q[T].Quek - 1))
      TmpTotal = (TmpTotal + (Fac[Len] * Inv[i] % MOD) * Inv[Len - i]) % MOD;
    for (register unsigned i((n - Q[T].Quex)/ Q[T].Quek), Len(n - Q[T].Quex - Q[T].Quek * i + i); i; --i, Len += (Q[T].Quek - 1))
      TmpLe = (TmpLe + (Fac[Len] * Inv[i] % MOD) * Inv[Len - i]) % MOD;
    for (register unsigned i((Q[T].Quex - 1) / Q[T].Quek), Len(Q[T].Quex - 1 - Q[T].Quek * i + i); i; --i, Len += (Q[T].Quek - 1))
      TmpRi = (TmpRi + (Fac[Len] * Inv[i] % MOD) * Inv[Len - i]) % MOD;
    Q[Q[T].Num].Ans = MOD + TmpTotal - (TmpLe * TmpRi % MOD), TmpTotal = TmpLe = TmpRi = 1;
    if(Q[Q[T].Num].Ans >= MOD) Q[Q[T].Num].Ans -= MOD;
  }
  for (register unsigned i(1); i <= m; ++i) printf("%u\\n", Q[i].Ans);
  return 0;
}

以上是关于省队集训Ⅱ-Day8的主要内容,如果未能解决你的问题,请参考以下文章

省队集训Day1 睡觉困难综合征

福建省队集训 20180709

福建省队集训 20180709

湖南省队集训 -- 1

省队集训Day1 总统选举

省队集训 Day6 序列