[TJOI2016/HEOI2016] 求和 线性解法

题意:求
∑ i = 0 n ∑ j = 0 i \{ i j \} ⋅ 2 j ⋅ j ! \sum_{i=0}^n \sum_{j=0}^i {i \brace j} \cdot 2^j \cdot j! i=0nj=0i{ji}2jj!
同余 998244353 998244353 998244353
翻了一下各 oj 这题的榜,无一例外都是 Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn) 的 NTT 做法,挺奇怪的……

现在来正经推一下式子:
a n s = ∑ i = 0 n ∑ j = 0 i j ! ⋅ \{ i j \} ⋅ 2 j = ∑ i = 0 n ∑ j = 0 n j ! ⋅ \{ i j \} ⋅ 2 j = ∑ i = 0 n ∑ j = 0 n 2 j ∑ k = 0 j ( − 1 ) j − k ( j k ) k i = ∑ j = 0 n ∑ k = 0 j ( − 1 ) j − k ( j k ) 2 j [ n + 1 ] k [ n ] q = ∑ i = 0 n − 1 q i is q-analog \begin{aligned} ans & = \sum_{i=0}^n \sum_{j=0}^i j!\cdot {i \brace j} \cdot 2^j \\ & = \sum_{i=0}^n \sum_{j=0}^n j!\cdot {i \brace j} \cdot 2^j \\ &= \sum_{i=0}^n\sum_{j=0}^n 2^j \sum_{k=0}^j (-1)^{j-k} \binom jk k^i \\ &= \sum_{j=0}^n \sum_{k=0}^j (-1)^{j-k}\binom jk 2^j [n+1]_k \quad [n]_q = \sum_{i=0}^{n-1}q^i \quad\text{is \href{https://en.wikipedia.org/wiki/Q-analog}{q-analog}} \end{aligned} ans=i=0nj=0ij!{ji}2j=i=0nj=0nj!{ji}2j=i=0nj=0n2jk=0j(1)jk(kj)ki=j=0nk=0j(1)jk(kj)2j[n+1]k[n]q=i=0n1qiis q-analog

大部分人的推导在这里就结束了,因为这个式子可以卷积。但是其实这个式子还可以不卷积进行计算。这个东西的要点就是我们要看清楚形如 ∑ j = i n q j − i ( j i ) a j \sum_{j = i}^n q^{j-i} \binom ji a_j j=inqji(ij)aj 的形式是什么。设数列 ⟨ a n ⟩ \langle a_n\rangle an 的母函数是 G ( z ) G(z) G(z),那么这个和式其实就是带入了 G ( z + q ) G(z+q) G(z+q)

反观我们的式子中, q = − 1 q = -1 q=1 G ( z ) = [ n + 1 ] 2 z = ( 2 z ) n + 1 − 1 2 z − 1 G(z) = [n + 1]_{2z} = \frac{(2z)^{n+1} - 1}{2z - 1} G(z)=[n+1]2z=2z1(2z)n+11

因此,我们得到的 [ n + 1 ] k [n+1]_k [n+1]k 项的系数就由

G ( z − 1 ) = ( 2 z − 2 ) n + 1 − 1 2 z − 3 G(z-1) = \frac{(2z-2)^{n+1} -1}{2z-3} G(z1)=2z3(2z2)n+11

分子可以用组合数化开,然后除以一个一次式,是可以递推的。

至于把所有的 [ n + 1 ] k = k n + 1 − 1 k − 1 [n+1]_k = \frac{k^{n+1}-1}{k-1} [n+1]k=k1kn+11 算出来为什么没有快速幂的 Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn),那是因为 k n + 1 k^{n+1} kn+1 关于 k k k 是完全积性函数,合数的 k k k 是可以被筛出来的,这样这部分复杂度就是 π ( n ) ⋅ Θ ( log ⁡ n ) = Θ ( n ) \pi(n) \cdot \Theta(\log n) = \Theta(n) π(n)Θ(logn)=Θ(n)

代码:

#include <cstdio>
#include <cstring>

#include <functional>
#include <vector>

#define LOG(FMT...) fprintf(stderr, FMT)

using namespace std;

typedef long long ll;

const int P = 998244353;

void exGcd(int a, int b, int& x, int& y) {
  if (!b) {
    x = 1;
    y = 0;
    return;
  }
  exGcd(b, a % b, y, x);
  y -= a / b * x;
}

int inv(int a) {
  int x, y;
  exGcd(a, P, x, y);
  if (x < 0)
    x += P;
  return x;
}

int mpow(int x, int k) {
  int ret = 1;
  while (k) {
    if (k & 1)
      ret = ret * (ll)x % P;
    x = x * (ll)x % P;
    k >>= 1;
  }
  return ret;
}

struct Simple {
  int n;
  vector<int> fac, ifac, inv;

  void build(int n) {
    this->n = n;
    fac.resize(n + 1);
    ifac.resize(n + 1);
    inv.resize(n + 1);
    fac[0] = 1;
    for (int x = 1; x <= n; ++x)
      fac[x] = fac[x - 1] * (ll)x % P;
    inv[1] = 1;
    for (int x = 2; x <= n; ++x)
      inv[x] = -(P / x) * (ll)inv[P % x] % P + P;
    ifac[0] = 1;
    for (int x = 1; x <= n; ++x)
      ifac[x] = ifac[x - 1] * (ll)inv[x] % P;
  }

  Simple() {
    build(1);
  }

  void check(int k) {
    int nn = n;
    if (k > nn) {
      while (k > nn)
        nn <<= 1;
      build(nn);
    }
  }

  int gfac(int k) {
    check(k);
    return fac[k];
  }

  int gifac(int k) {
    check(k);
    return ifac[k];
  }

  int ginv(int k) {
    check(k);
    return inv[k];
  }

  int binom(int n, int m) {
    if (m < 0 || m > n)
      return 0;
    return gfac(n) * (ll)gifac(m) % P * gifac(n - m) % P;
  }
} simp;

const int N = 100010, PC = 30010;

int n;
int pc;
int a[N];
bool vis[N];
int p[PC];
int pw[N];

void sieve() {
  pw[1] = 1;
  for (int x = 2; x <= n; ++x) {
    if (!vis[x]) {
      p[++pc] = x;
      pw[x] = mpow(x, n + 1);
    }
    for (int i = 1; x * p[i] <= n; ++i) {
      vis[x * p[i]] = true;
      pw[x * p[i]] = pw[x] * (ll)pw[p[i]] % P;
      if (x % p[i] == 0)
        break;
    }
  }
}

int main() {
  scanf("%d", &n);
  simp.check(n + 1);
  for (int i = 0; i <= n; ++i)
    a[i] = ((n + 1 - i) & 1) ? (P - simp.binom(n + 1, i)) : simp.binom(n + 1, i);
  int pw = mpow(2, n + 1);
  for (int i = 0; i <= n; ++i)
    a[i] = a[i] * (ll)pw % P;
  --a[0];
  for (int i = 0; i <= n; ++i)
    a[i] = (P - a[i]) % P;
  int q = inv(3) * 2 % P;
  for (int i = 1; i <= n; ++i)
    a[i] = (a[i - 1] * (ll)q + a[i]) % P;
  int ans = (a[0] + a[1] * (ll)(n + 1)) % P;
  sieve();
  for (int i = 2; i <= n; ++i)
    ans = (ans + simp.ginv(i - 1) * (::pw[i] - 1LL) % P * a[i]) % P;
  if (ans < 0)
    ans += P;
  ans = ans * (ll)inv(3) % P;
  printf("%d\n", ans);

  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值