【NOI2019】 机器人 【区间dp】【插值】【下降幂多项式】【分段函数】

题意:有个长度为 n n n的序列 a a a a i ∈ [ L i , R i ] a_i\in [L_i,R_i] ai[Li,Ri]。从一个位置 s s s可以往左直到 ≥ a s \geq a_s as,往右直到 > a s > a_s >as。求有多少种可能的序列满足从任意位置向左或向右的最大步数的差的绝对值不超过 2 2 2

n ≤ 300 , R i ≤ 1 0 9 n\leq 300,R_i\leq 10^9 n300,Ri109

National Olympics in Interpolation

先对于一个给定序列判断是否合法

考虑最右边的最大值,它可以到序列的任意位置,所以它一定在序列正中间的 O ( 1 ) O(1) O(1)个位置。

左边的点都到不了右边,右边的点都到不了左边,所以可以分开考虑。

现在考虑计数。设 d p ( l , r , x ) dp(l,r,x) dp(l,r,x)表示 [ l , r ] [l,r] [l,r]这个区间最大值恰好为 x x x且满足题目中的要求的方案数

d p ( l , r , x ) = ∑ ∣ l + r − 2 k ∣ ≤ 2 [ ∑ i ≤ x d p ( l , k − 1 , i ) ] [ ∑ i < x d p ( k + 1 , r , i ) ] dp(l,r,x)=\sum _{|l+r-2k|\leq2}[\sum _{i\leq x}dp(l,k-1,i)][\sum_{i<x}dp(k+1,r,i)] dp(l,r,x)=l+r2k2[ixdp(l,k1,i)][i<xdp(k+1,r,i)]

根据套路,大胆猜想:

dp(l,r,x)是关于 x 的 r − l 次多项式 \text{dp(l,r,x)是关于$x$的$r-l$次多项式} dp(l,r,x)是关于xrl次多项式

证明很套路,略

先不管值域的限制,我们现在需要维护多项式求点值前缀和和多项式乘法

拉格朗日是个不错的方法,但是被凉心出题人卡了

这里需要用下降幂多项式的黑科技

首先有个显而易见的式子:

x i ‾ − ( x − 1 ) i ‾ = i ( x − 1 ) i − 1 ‾ x^{\underline i}-(x-1)^{\underline i}=i(x-1)^{\underline {i-1}} xi(x1)i=i(x1)i1

有什么用呢?

考虑一个下降幂多项式

f ( x ) = ∑ i a i x i ‾ f(x)=\sum_{i}a_ix^{\underline i} f(x)=iaixi

我们想用一些办法快速求它的前缀和。因为一些奇怪的原因,这里我们只加到 x − 1 x-1 x1,即不包括这个位置本身

g ( x ) = ∑ i = 1 x − 1 f ( i ) = ∑ i = 1 x − 1 ∑ j a j i j ‾ g(x)=\sum_{i=1}^{x-1}f(i)\\=\sum_{i=1}^{x-1}\sum_{j}a_ji^{\underline j} g(x)=i=1x1f(i)=i=1x1jajij

= ∑ j a j ∑ i = 1 x − 1 i j ‾ =\sum_j a_j\sum_{i=1}^{x-1} i^{\underline j} =jaji=1x1ij

对于一个已知的 i i i,考虑这个怎么求:

∑ x = 1 n x i ‾ \sum_{x=1}^nx^{\underline i} x=1nxi

用上面的式子裂下项

∑ x = 1 n ( x + 1 ) i + 1 ‾ − x i + 1 ‾ i + 1 \sum_{x=1}^n\frac{(x+1)^{\underline{i+1}}-x^{\underline {i+1}}}{i+1} x=1ni+1(x+1)i+1xi+1

= ( n + 1 ) i + 1 ‾ i + 1 =\frac{(n+1)^{^{\underline {i+1}}}}{i+1} =i+1(n+1)i+1

所以

g ( x ) = ∑ i a i x i + 1 ‾ i + 1 g(x)=\sum_i a_i\frac{x^{\underline {i+1}}}{i+1} g(x)=iaii+1xi+1

形式上和连续多项式的积分一样,不知道是不是巧合

再加上原来的多项式就得到了前缀和

对于两个下降幂多项式 f ( x ) , g ( x ) f(x),g(x) f(x),g(x)的乘法,可以从低到高枚举 f ( x ) f(x) f(x)的每一项,对于一个 x i ‾ x^{\underline i} xi,与右边的一个 ( x − i ) j ‾ (x-i)^{\underline j} (xi)j相乘可以得到 x i + j ‾ x^{\underline {i+j}} xi+j。每枚举一个 i i i就再用上面的式子 O ( n ) O(n) O(n)地把 g ( x ) g(x) g(x)变成 g ( x − 1 ) g(x-1) g(x1),然后暴力算卷积即可。

带上 L i , R i L_i,R_i Li,Ri的话就把 [ 1 , L i − 1 ] [1,L_i-1] [1,Li1] [ R i + 1 , + ∞ ] [R_i+1,+\infin] [Ri+1,+]的部分置为 0 0 0,直接大力维护分段函数即可

复杂度是什么?可以吃吗?

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <vector>
#include <utility>
#define MAXN 350
using namespace std;
const int MOD=1e9+7;
typedef long long ll;
inline int qpow(int a,int p)
{
	int ans=1;
	while (p)
	{
		if (p&1) ans=(ll)ans*a%MOD;
		a=(ll)a*a%MOD,p>>=1;
	}
	return ans;
}
int inv[MAXN];
inline int add(const int& x,const int& y){return x+y>=MOD? x+y-MOD:x+y;}
inline int dec(const int& x,const int& y){return x<y? x-y+MOD:x-y;}
typedef vector<int> poly;
typedef pair<poly,int> seg;
typedef vector<seg> func;
#define fir first
#define sec second
#define mp make_pair
inline int f(const poly& a,const int& x)
{
	int ans=0;
	for (int i=0,m=1;i<(int)a.size();m=(ll)m*(x-i)%MOD,++i)
		ans=(ans+(ll)a[i]*m)%MOD;
	return ans;
}
inline poly operator +(poly a,const poly& b)
{
	a.resize(max(a.size(),b.size()));
	for (int i=0;i<(int)b.size();i++) a[i]=add(a[i],b[i]);
	return a;
}
inline poly operator *(const poly& a,poly b)
{
	poly c(a.size()+b.size()-1);
	for (int i=0;i<(int)a.size();i++)
	{
		for (int j=0;j<(int)b.size();j++) c[i+j]=(c[i+j]+(ll)a[i]*b[j])%MOD;
		for (int j=1;j<(int)b.size();j++) b[j-1]=(b[j-1]+(ll)b[j]*j)%MOD;
	}
	return c;
}
inline poly integ(poly a)
{
	a.push_back(0);
	for (int i=a.size()-1;i>0;i--) a[i]=(ll)a[i-1]*inv[i]%MOD;
	a[0]=0;
	return a;
}
inline func operator +(func a,func b)
{
	func c;
	int i=0,j=0,pos=0;
	while (true)
	{
		c.push_back(mp(a[i].fir+b[j].fir,pos));
		if (i==(int)a.size()-1&&j==(int)b.size()-1) break;
		if (i<(int)a.size()-1&&(j==(int)b.size()-1||a[i+1].sec<b[j+1].sec)) pos=a[++i].sec;
		else pos=b[++j].sec;
		if (i<(int)a.size()-1&&a[i+1].sec<=pos) ++i;
		if (j<(int)b.size()-1&&b[j+1].sec<=pos) ++j;
	}
	return c;
}
inline func operator *(func a,func b)
{
	func c;
	int i=0,j=0,pos=0;
	while (true)
	{
		c.push_back(mp(a[i].fir*b[j].fir,pos));
		if (i==(int)a.size()-1&&j==(int)b.size()-1) break;
		if (i<(int)a.size()-1&&(j==(int)b.size()-1||a[i+1].sec<b[j+1].sec)) pos=a[++i].sec;
		else pos=b[++j].sec;
		if (i<(int)a.size()-1&&a[i+1].sec<=pos) ++i;
		if (j<(int)b.size()-1&&b[j+1].sec<=pos) ++j;
	}
	return c;
}
inline func integ(func a)
{
	for (int i=0;i<(int)a.size();i++)
	{
		a[i].fir=integ(a[i].fir);
		if (i) a[i].fir[0]=dec(f(a[i-1].fir,a[i].sec),f(a[i].fir,a[i].sec));
	}
	return a;
}
inline func cut(const func& a,const int& l,const int& r)
{
	func b;
	b.push_back(mp(poly(1),0));
	for (int i=0;i<(int)a.size();i++)
		if (a[i].sec<=r&&(i==(int)a.size()-1||l<a[i+1].sec))
			b.push_back(mp(a[i].fir,max(a[i].sec,l)));
	b.push_back(mp(poly(1),r+1));
	return b;
}
func dp[MAXN][MAXN];
int L[MAXN],R[MAXN];
void dfs(int l,int r)
{
	if (dp[l][r].size()) return;
	dp[l][r].push_back(mp(poly(1),0));
	if (l>r) return (void)(dp[l][r][0].fir[0]=1);
	for (int k=l;k<=r;k++)
		if (-2<=l+r-2*k&&l+r-2*k<=2)
		{
			dfs(l,k-1),dfs(k+1,r);
			func a=dp[l][k-1],b=dp[k+1][r];
			if (l<k) a=a+integ(a);
			if (k<r) b=integ(b);
			dp[l][r]=dp[l][r]+cut(a*b,L[k],R[k]); 
		}
}
int main()
{
	inv[1]=1;
	for (int i=2;i<MAXN;i++) inv[i]=(ll)inv[MOD%i]*(MOD-MOD/i)%MOD;
	int n;
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d%d",&L[i],&R[i]);
	dfs(1,n);	
	printf("%d\n",integ(dp[1][n]).back().fir[0]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值