\(n^2\) 暴力很好想,但我们先找到一种常数小的 \(n^2\) 做法。
考虑组合意义,这种 \(a_i , b_i\) 的形式很容易让人联想到网格图的路径方案数。
于是令 \(f_{a_i,b_i} = 1\) 为初始状态,\(f_{i,j} = f_{i + 1 , j} + f_{i , j + 1}\)。
那么 \(f_{0,k}\) 就是我们的答案。由于只有加法,常数很小,所以可以跑 40pts。
由于答案最后是分布在一列上,我们不妨用生成函数来考虑这件事。我们把一列的 dp 值写成一个生成函数,转移一列就相当于滚了一次前缀和。
滚前缀和相当于乘了一个 \(T(x) = \frac{1}{1 - x}\)(封闭形式)。
但我们还需要在滚前缀和过程中在个别位置加上 \(1\) 作为初始值。由于每个 \(T(x)\) 的长度是 \(O(n)\) 的,因此直接加很不方便。
考虑分块,设块长大小为 \(B\)。则我们每次滚 \(B\) 次前缀和,即每次乘以 \(T^B(x)\)。但这 \(B\) 列中可能还有一些计算点,这些计算点到我们新的当前列可能只需要乘以 \(T^{B - d}(x)\)。
因此我们把这个点值乘以 \((1 - x)^d\) 加到我们滚 \(B\) 次前缀和的多项式上面就好了。注意到 \(d < B\) ,所以总复杂度为 \(O(\frac{n}{B} n \log n + nB)\)。
所以,\(B = \sqrt{n \log n}\) 时最优。
由于是多项式题,所以常数巨大。
#include <map>
#include <set>
#include <ctime>
#include <queue>
#include <cmath>
#include <bitset>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define pii pair <int , int>
#define pll pair <LL , LL>
#define mp make_pair
#define fs first
#define sc second
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
//const int Mxdt=100000;
//static char buf[Mxdt],*p1=buf,*p2=buf;
//#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
template <typename T>
void read(T &x) {
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9') {if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=(x<<3)+(x<<1)+(s-'0');s=getchar();}
x *= f;
}
template <typename T>
void write(T x , char s='\n') {
if(!x) {putchar('0');putchar(s);return;}
if(x<0) {putchar('-');x=-x;}
T tmp[25]={},t=0;
while(x) tmp[t++]=x%10,x/=10;
while(t-->0) putchar(tmp[t]+'0');
putchar(s);
}
#define poly vector <int>
#define Len(x) (int)x.size()
const int MAXN = 1e5 + 5;
const int B = 1414;
//const int B = 1;
const int mod = 998244353;
inline int Add(int x , int y) {x += y;return x > mod?x - mod:x;}
inline int Mul(int x , int y) {return 1ll * x * y % mod;}
inline int Sub(int x , int y) {x -= y;return x < 0?x + mod:x;}
vector <int> P[MAXN];
int qpow(int a , int b) {
int res = 1;
while(b) {
if(b & 1) res = Mul(res , a);
a = Mul(a , a);
b >>= 1;
}
return res;
}
int r[MAXN << 2];
void NTT(poly &F , int op) {
for (int i = 0; i < Len(F); ++i) if(i < r[i]) swap(F[i] , F[r[i]]);
for (int k = 1 , t = 1; k < Len(F); k <<= 1 , t ++) {
int ori = qpow(114514 , (op == 1)?(mod - 1) / (k << 1):(mod - 1) - (mod - 1) / (k << 1));
for (int i = 0; i < (Len(F) >> t); ++i) {
int now = 1;
for (int j = 0; j < k; ++j) {
int cur = Mul(F[(i << t) ^ j ^ k] , now);
F[(i << t) ^ j ^ k] = Sub(F[(i << t) ^ j] , cur);
F[(i << t) ^ j] = Add(F[(i << t) ^ j] , cur);
now = Mul(now , ori);
}
}
}
if(op == -1) {
int D = qpow(Len(F) , mod - 2);
for (int i = 0; i < Len(F); ++i) F[i] = Mul(F[i] , D);
}
}
poly operator * (poly F , poly G) {
poly FG;
int rl = Len(F) + Len(G) - 1 , n = Len(F) , m = Len(G) , N = 1;
while(N <= rl) N <<= 1;
FG.resize(N) , F.resize(N) , G.resize(N);
for (int i = 0; i < N; ++i) {
r[i] = (r[i >> 1] >> 1) | ((i & 1)?(N >> 1):0);
FG[i] = 0;
if(i >= n) F[i] = 0;
if(i >= m) G[i] = 0;
}
NTT(F , 1) , NTT(G , 1);
for (int i = 0; i < N; ++i) FG[i] = Mul(F[i] , G[i]);
NTT(FG , -1);
FG.resize(rl);
return FG;
}
int n , a[MAXN] , b[MAXN];
int fac[MAXN << 1] , finv[MAXN << 1];
int C(int n , int m) {
return Mul(fac[n] , Mul(finv[n - m] , finv[m]));
}
poly Make_poly(int n , int B) {
poly res;res.resize(n);
for (int i = 0; i < n; ++i) res[i] = C(i + B - 1 , B - 1);
return res;
}
poly ot , t , s[B + 5];
int main() {
read(n);
for (int i = 1; i <= n; ++i) read(a[i]) , read(b[i]) , P[a[i]].push_back(b[i]);
fac[0] = 1;
for (int i = 1; i <= n * 2; ++i) fac[i] = Mul(fac[i - 1] , i);
finv[n * 2] = qpow(fac[n * 2] , mod - 2);
for (int i = n * 2; i >= 1; --i) finv[i - 1] = Mul(finv[i] , i);
// ot.reserve(n + 1);
// for (int i = 0; i <= n; ++i) ot.push_back(1);
t = Make_poly(n , B);
// for (int i = 0; i <= n; ++i) write(t[i] , ' ');puts("");
s[0].push_back(1);
s[1].push_back(1) , s[1].push_back(Sub(0 , 1));
for (int i = 2; i <= B + 1; ++i) s[i] = s[i / 2] * s[(i + 1) / 2];
poly cur;cur.resize(n);
for (int i = n - 1; i >= 0; i -= B) {
for (int j = i; j >= max(i - B + 1 , 0); --j) {
for (auto p:P[j]) {
int bg = n - p - 1;
for (int k = 0; k < Len(s[i - j]) && k + bg < n; ++k) {
cur[k + bg] = Add(cur[k + bg] , s[i - j][k]);
}
}
}
if(i >= B) cur = cur * t;
else cur = cur * Make_poly(n , i + 1);
cur.resize(n);
// for (int i = 0; i < n; ++i) write(cur[i] , ' ');puts("");
}
for (int i = n - 1; i >= 0; --i) write(cur[i] , ' ');
return 0;
}