• Easy Sum


    Link

    \(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;
    }
    
  • 相关阅读:
    单链表的算法
    顺序表的算法
    程序员的内功——数据结构和算法系列
    查找一 线性表的查找

    排序算法系列
    排序三 直接插入排序
    排序八 基数排序
    Linux编程 9 (shell类型,shell父子关系,子shell用法)
    mysql 开发进阶篇系列 41 mysql日志之慢查询日志
  • 原文地址:https://www.cnblogs.com/Reanap/p/16410907.html
Copyright © 2020-2023  润新知