省队集训Ⅱ-Day8

Day8

Color

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

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

color-40'.cpp

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

fi=jj+kifj

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

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

对于点 x 是不涂色的, 分两部分考虑, 枚举它左边的最近的黑点 i, 右边最近的黑点 j. 对于每对 jik 的点, 答案就是 fifnj+1, 不要忘了左边不涂, 右边不涂和左右都不涂的情况.

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

总复杂度 O(nlogn+n2(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' 了, 显然我对我的时间复杂度没有准确的估计.

这个版本的更新是设 Sumi=j=1j<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.

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

fi=j=1j+kifj

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

fi=1+j=1jmax(ik,1)fj

方程的正确性是这样保证的: 原来的 f1k=1 所转移的情况是除了 i 点涂黑, 其它的点都留白的情况. 对于 fi[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'Ex_Pro_Max.cpp

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

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'Ex_Pro_Max_Ti.cpp

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

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

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'Ex_Pro_Max_Ti_X.cpp

重新审视这个询问的过程, 发现所有的方案数 Sumi+1, 减去 x 涂黑的方案数 fxfnx+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'Ex_Pro_Max_Ti_X_S.cpp

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

这是我考试时最后一个版本, 这份代码主要是对 DP 的过程进行剪枝, 因为打表可得, 对于某个 k, fi[1,k] 等于 1. 又因为排序后 k 单增, 所以我们自然不用每次再求一遍 fi[1,k] 了, 复杂度变成 O(nk), 对于 k 大的情况下理论上能将整个程序复杂度优化 50% 左右. 所以总复杂度 O(nlogn+nk2+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(nmk) 做法. 我一听也很疑惑, 我的算法 k 在分子上, 为什么你的 k 在分子上?

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

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

对于 i[1,n+k1k](n+k1ki+ii), 就能得到对于 n 长度和 k 间隔没有约束条件的合法涂色方案数.

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

x 为黑的情况, 我们知道 i[xk+1,x1][x+1,x+k1] 必须是白点. 所以 x 右边最多有 nxk, 左边最多有 x1+k1k+1k=x1k. 对于 i>x 由于黑点的尾巴在左边, 所以带尾巴的黑点可以贴脸放, 而且不需要像求整段一样在左端加上 k1 个点, 则 x 右边放 i 个黑点的合法方案数为 (nxki+ii), 则 x 的左边放 i 个的合法方案数为 (x1ki+ii).

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

(i=1in+k1k(n+k1ki+ii))(i=1inxk(nxki+ii))(i=1ix1k(x1ki+ii))

只要可以 O(1) 求二项式系数, 我们就可以 O(nk) 回答询问了.

众所周知, 二项式系数:

(ab)=a!b!(ab)!

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

要预处理 invi!,i[1,n], 我们需要先用快速幂求出 invn!.

假设 invi! 已经求出, 要求 inv(i1)! 使得 (i1)!inv(i1)!1(modp).

i!inv(i1)!i(modp), 又因为 i!invi!1(modp), 所以 inv(i1)!invi!i(modp).

这样, 总的复杂度就是 O(nmk) 了.

接下来考虑正解.

我们现在有算法可以做到 O(nlogn+nk2+m), 也有算法能做到 O(nmk). 所以可以数据分治, 对于 kn 的询问, 用前一种算法, 对于 k>n 的做法, 用后一种做法, 这样, 最坏的复杂度是 O(nlogn+(n+m)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;
}
posted @   Wild_Donkey  阅读(42)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示