20200721 T3 s3mple【DP,点值优化卷积】

题目描述

在这里插入图片描述
在这里插入图片描述
n ≤ 200 , x ≤ 1 0 9 n\le200,x\le 10^9 n200,x109

题目分析

容易想到枚举最大值的位置,然后递归两边,于是有这样的DP:
在这里插入图片描述
上面式子里面有个地方要改成 f ( n − 1 − i , m − m i n ( i , n − 1 − i ) − 1 − j ) f(n-1-i,m-min(i,n-1-i)-1-j) f(n1i,mmin(i,n1i)1j)

可以DP求出区间长度为 n n n x x x 的最大值: m x [ n ] = m i n ( n + 1 2 , n + 1 − n + 1 2 ) mx[n]=min(\frac {n+1}2,n+1-\frac {n+1}2) mx[n]=min(2n+1,n+12n+1)

事实上已经可以通过DP的范围限制以及常数优化通过此题了,比如这么写:

//code by skyh, orz.
#include<bits/stdc++.h>
using namespace std;
int mod,n,x,T;
int fir[15],sec[15];
int dp[205][805],C[205][205],f[205];
const unsigned long long inf=1.4e19;
int main(){
	freopen("s3mple.in","r",stdin);
	freopen("s3mple.out","w",stdout);
	scanf("%d",&mod); dp[0][0]=1; dp[1][1]=1;
	for(int i=(f[1]=1,2);i<=200;++i) for(int j=1;j<=i;++j) f[i]=max(f[i],f[j-1]+f[i-j]+min(j,i-j+1));
	for(int i=0;i<=200;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	for(;scanf("%d%d",&fir[T+1],&sec[T+1])!=EOF;++T,n=max(n,fir[T]));
	for(register int l=2;l<=n;++l)
		for(register int i=1;i<=l;++i){
			const int add=min(i,l-i+1);
			for(register int j=l-1;j<=f[l]-add;++j){
				register unsigned long long y=0;
				const int lp=max(i-1,j-f[l-i]),rp=min(f[i-1],j-l+i);
				register int k=lp;
				for(k=lp;k+4<=rp;){
					y+=1ll*dp[i-1][k]*dp[l-i][j-k];++k;
					y+=1ll*dp[i-1][k]*dp[l-i][j-k];++k;
					y+=1ll*dp[i-1][k]*dp[l-i][j-k];++k;
					y+=1ll*dp[i-1][k]*dp[l-i][j-k];++k;
					if(y>inf) y%=mod;
				}
				for(;k<=rp;++k){
					y+=1ll*dp[i-1][k]*dp[l-i][j-k];
					if(y>inf) y%=mod;
				}
				(dp[l][j+add]+=y%mod*C[l-1][i-1]%mod)%=mod;
			}
		}
	for(int i=1;i<=T;++i) printf("%d\n",sec[i]>f[fir[i]]?0:dp[fir[i]][sec[i]]);
	return 0;
}

还可以继续优化
转移相当于是在枚举 i i i m m m 做卷积,于是可以将 f ( n , m ) f(n,m) f(n,m) 看做多项式 F n ( x ) = ∑ m f ( n , m ) x m F_n(x)=\sum_{m}f(n,m)x^m Fn(x)=mf(n,m)xm
次数就是权值,系数就是方案数。

F n ( x ) = ∑ i = 0 n − 1 F i ( x ) ∗ F n − i − 1 ( x ) ∗ ( n − 1 i ) ∗ x m i n ( i , n − 1 − i ) + 1 F_n(x)=\sum_{i=0}^{n-1}F_i(x)*F_{n-i-1}(x)*\binom {n-1}i*x^{min(i,n-1-i)+1} Fn(x)=i=0n1Fi(x)Fni1(x)(in1)xmin(i,n1i)+1

F 0 ( x ) = 1 , F 1 ( x ) = x F_0(x)=1,F_1(x)=x F0(x)=1,F1(x)=x

如果模数是998244353,可以NTT, O ( n 3 log ⁡ 2 n ) O(n^3\log^2 n) O(n3log2n)

因为 F F F 的最高次项不过 n log ⁡ n n\log n nlogn,不会循环,可以把 F F F 转为点值表达式做卷积,那么转移复杂度 O ( n 2 ∗ n log ⁡ n ) O(n^2*n\log n) O(n2nlogn)
询问 n , v n,v n,v,就拉格朗日插值还原 F n ( x ) F_n(x) Fn(x) 的系数表达式,答案就是 x v x^v xv 项的系数。
插值可以预处理 G i ( x ) = ∏ i ≠ j x − j G_i(x)=\prod_{i\neq j} x-j Gi(x)=i=jxj,那么每次求答案就是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的,预处理 O ( ( n log ⁡ n ) 2 ) O((n\log n)^2) O((nlogn)2)

Code:

#include<bits/stdc++.h>
using namespace std;
const int M = 735, maxn = 205;
int n,X,mod,f[maxn][M+5],F[M+5],G[M+5][M+5],C[maxn][maxn],fac[M+5],inv[M+5],pw[M+5][maxn];
void Mul(int *f,int t){
	for(int i=M+1;i;i--) f[i]=(f[i-1]-1ll*f[i]*t)%mod;
	f[0]=1ll*f[0]*-t%mod;
}
void Div(int *f,int t){
	int pre=f[M+1],now; f[M+1]=0;
	for(int i=M;i>=0;i--,pre=now) now=f[i],f[i]=(pre+1ll*f[i+1]*t)%mod;
}
int main()
{
	freopen("s3mple.in","r",stdin);
	freopen("s3mple.out","w",stdout);
	scanf("%d",&mod);
	for(int i=0;i<maxn;i++)
		for(int j=C[i][0]=1;j<=i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	for(int i=0;i<=M;i++) for(int j=pw[i][0]=1;j<maxn;j++) pw[i][j]=1ll*pw[i][j-1]*i%mod;
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<=M;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=M;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
	for(int i=0;i<=M;i++) f[0][i]=1,f[1][i]=i;
	for(n=2;n<=200;n++)
		for(int j=0;j<=M;j++)
			for(int i=0;i<n;i++)
				f[n][j]=(f[n][j]+1ll*f[i][j]*f[n-1-i][j]%mod*C[n-1][i]%mod*pw[j][min(i,n-1-i)+1])%mod;
	//for(int i=0;i<=5;i++) cout<<f[3][i]<<' '; cout<<endl;
	//cerr<<clock()<<endl;
	F[0]=1;
	for(int i=0;i<=M;i++) Mul(F,i);
	for(int i=0;i<=M;i++) memcpy(G[i],F,sizeof F),Div(G[i],i);
	while(~scanf("%d%d",&n,&X)){
		if(X>M) {puts("0");continue;}
		int ans=0;
		for(int i=0;i<=M;i++){
			int coef = 1ll*(M-i&1?-1:1)*f[n][i]*inv[i]%mod*inv[M-i]%mod;
			ans=(ans+1ll*coef*G[i][X])%mod;
		}
		printf("%d\n",(ans+mod)%mod);
	}
}
/*
int solve(int l,int r){
	if(l>r) return 0; 
	int mid=(l+r)>>1;
	return solve(l,mid-1)+solve(mid+1,r)+min(mid-l+1,r-mid+1);
}
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值