CF1874E Jellyfish and Hack

CF1874E Jellyfish and Hack

题意

函数 \(fun(P)\) 是每次选择第一个数作为哨兵的快排,返回排序使用的单位时间。\(P\) 是排列。\(P\) 的长度是 \(n\)

求有多少个排列 \(P\) 满足 \(lim \le fun(P)\)

\(n \le 200,lim \le 10^9\)

时限 \(8s\)

思路

最长用时不超过 \(\frac{n(n+1)}{2}\)

考虑 \(O(n^4)\) 的 DP。

先写一个复杂度爆炸的 DP。

\(f_{a,b}\) 表示长度为 \(a\) 的排列 \(P\)\(fun(P)=b\)\(P\) 的个数。

如何转移?枚举 \(P\) 第一个数 \(x\) 是什么,然后把排列分成 \(<x, =x, >x\) 三个部分转移。

\[f_{a,b} = \sum_{x=1}^a f_{x-1,s_1} \times f_{a-x,s_2} \times \binom{a-1}{x-1}(s_1+s_2+a=b) \]

时间复杂度是:状态是 \(n \times n^2\),枚举 \(x\)\(n\),枚举 \(s\)\(n^2\),总共 \(O(n^6)\)

这个 \(s_1+s_2+a=b\) 像卷积。于是我们把它写成卷积的形式吧。

\[\begin{aligned} \frac{f_{a,b}}{(a-1)!} & = \sum_{x=1}^a \frac{f_{x-1,s_1}}{(x-1)!} \times \frac{f_{a-x,s_2}}{(a-x)!}\\ g_{i,x} & = \frac{f_{i,x}}{i!}\\ ag_{a,b} & = \sum_{x=1}^a g_{x-1,s_1} \times g_{a-x,s_2} (s_1+s_2+a=b)\\ g_a(x) & = \frac{x^a}{a} \sum_{i=1}^a g_{i-1}(x) \times g_{a-i}(x) \end{aligned} \]

一共要做 \(n \times n\) 次长度为 \(n^2\) 的多项式的乘法,如果用 NTT 的话,\(O(n^4 \log n)\) 的复杂度还是太高了。而且这题的模数告诉我们不能用 NTT。

注意到一个多项式会和其他多项式相乘多次,而且我们最后只要 \(g_n(x)\) 这个多项式的插值。所以想到可以用点值计算,最后做个 \(O(n^4)\) 或者 \(O(n^2)\) 的拉插。

用点值算就没有 \(\log\),所以总复杂度是 \(O(n^4)\)

详细地说,答案需要求出系数,然后把次数 \(>=lim\) 的系数加起来。

其实如果是算点值的话,我们可以直接用这个公式?不要 \(g\) 了。

\[f_a(x) = x^a \sum_{i=1}^a \binom{a-1}{i-1} f_{i-1}(x) f_{a-i}(x) \]

一些细节:

  • 操作次数最多是 \(\frac{n(n+1)}{2}\) 次,所以多项式次数是 \(\frac{n(n+1)}{2}\),所以需要 \(\frac{n(n+1)}{2}+1\) 个点值。

拉插公式:

\[f(x) = \sum_{i=1}^n y_i \prod_{j\neq i} \frac{x-x_j}{x_i-x_j} \]

拉插介绍

很棒的题。我的第一道拉插题。

code

好多细节,好难写,好难调。毕竟是第一道拉插题,多写就会了。

数组没开够,以后编译一定开 -fsanitize

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
	constexpr int N=203,mod=1e9+7;
	int add(int a,int b) { return a+b>=mod ? a+b-mod : a+b; }
	void _add(int &a,int b) { a=add(a,b); }
	int mul(int a,int b) { return 1ll*a*b%mod; }
	void _mul(int &a,int b) { a=mul(a,b); }
	int ksm(int a,int b=mod-2) {
		int s=1;
		while(b) {
			if(b&1) _mul(s,a);
			_mul(a,a);
			b>>=1;
		}
		return s;
	}
	int n,lim,m;
	int f[N*N],c[N][N],p[N*N][N],y[N*N],M[N*N],px[N*N],ans[N*N],sum;
    void main() {
		sf("%d%d",&n,&lim);
		m=n*(n+1)/2+1;
		//预处理
		c[0][0]=1;
		rep(i,1,n) {
			c[i][0]=1;
			rep(j,1,i) c[i][j]=add(c[i-1][j-1],c[i-1][j]);
		}
		rep(i,1,m) {
			p[i][0]=1;
			rep(j,1,n) p[i][j]=mul(p[i][j-1],i);
		}
		//计算点值
		rep(x,1,m) {
			f[0]=1;
			rep(a,1,n) {
				int s=0;
				rep(i,1,a) {
					_add(s,mul(c[a-1][i-1],mul(f[i-1],f[a-i])));
				}
				f[a]=mul(p[x][a],s);
			}
			y[x]=f[n];
		}
		//拉插求系数
		//多项式 M(x) = \prod_{i=1}^{m} (x-x_i),数组存的是系数
		M[0]=1;
		rep(i,1,m) {
			per(j,i,0) {
				_add(M[j+1],M[j]);
				_mul(M[j],mod-i);
			}
		}
		//px[i] = \prod_{j!=i} (x_i-x_j)
		rep(i,1,m) {
			px[i]=1;
			rep(j,1,m) if(j^i) {
				_mul(px[i],add(i,mod-j));
			}
			//顺便把倒数取了,把 y_i 乘上
			px[i]=ksm(px[i]);
			_mul(px[i],y[i]);
		}
		//\sum_i px[i] * M(x) / (x-x_i)
		rep(i,1,m) {
			int p=M[m];
			per(j,m-1,0) {
				_add(ans[j],mul(px[i],p));
				p=add(M[j],mul(p,i));
			}
			// rep(i,0,m-1) pf("%d ",ans[i]); pf("\n");
		}
		// rep(i,0,m-1) pf("%d ",ans[i]); pf("\n");
		rep(i,lim,m-1) _add(sum,ans[i]);
		pf("%d\n",sum);
    }
}
int main() {
    #ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("my.out","w",stdout);
    #endif
    wing_heart :: main();
}
posted @ 2025-05-31 16:47  wing_heart  阅读(18)  评论(0)    收藏  举报