「NOI2019」机器人 (维护多项式dp值)

「NOI2019」机器人 (维护多项式dp值)

写的时候觉得这个题整个复杂度升天,代码升天,然后发现正解居然可以真的这样模拟。。。

(而且自己拉格朗日不熟练所以就咕咕咕。。。)

Part 35-50

首先是一个简单的区间dp

定义\(dp[l][r][Max]\)为区间\([l,r]\)中最大的数为\(Max\)的方案数

转移上,类似构建笛卡尔树,枚举区间中最大的点\(k\)

由于题目的限制,我们令\(Max_{lson}\leq Val_k<Max_{rson}\)

这样,\(k\)这个点行走的区域就是\([l,k-1],[k+1,r]\),带入题目的限制即可

复杂度\(O(n^3+n^2 Max)\)

但是由于两边的访问区间极其接近二叉树,所以\(\text{dfs}\)之后可以发现,所需访问的节点完全达不到\(O(n^2)\),接近\(10n\)

所以直接用dfs,状态前两维标号就可以了

Part100

考虑边界条件\(dp[i][i]\)

可以发现

\(dp[i][i][j]=\left\{ \begin{aligned} 0 && j<L_i \\ 1 && j\in [L_i,R_i] \\ 0 && j>R_i\end{aligned}\right.\)

这是一个分段的0次多项式(??)

转移

\(dp[l][r][Max]\leftarrow dp[l][k-1][1..Max]\cdot dp[k+1][r][1..Max-1]\)

我们知道任何一个\(n\)次多项式的前缀和都可以表示为一个\(n+1\)次多项式

所以可以归纳得到\(dp[i][j][k]\)是一个关于\(k\)\(j-i\)次的分段多项式

其中分段数显然是\(O(r-l)\)

那么我们考虑每次合并两个前缀和,然后相乘得到\(dp[i][j][k]\),再自己进行累和

如何将前缀和转化为多项式?分成\(n\)\(i^k\)前缀和,然后通过拉格朗日插值法得到

不要问我复杂度是多少,我们都是有梦想的人。。

#include<bits/stdc++.h>
using namespace std;

#define reg register
typedef long long ll;
#define rep(i,a,b) for(int i=a,i##end=b; i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b; i>=i##end;--i)

#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define pb push_back
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
int rd(){
	int s=0,f=0;
	while(!isdigit(IO=getchar())) if(IO=='-') f=1;
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return f?-s:s;
}

const int N=310,P=1e9+7;

bool be;

int n;
int L[N],R[N];

typedef vector <int> Poly;
// 多项式计算部分
ll qpow(ll x,ll k=P-2) {
	ll res=1;
	for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
	return res;
}
Poly operator * (const Poly a,const Poly b) {
	Poly res(a.size()+b.size()-1);
	rep(i,0,a.size()-1) rep(j,0,b.size()-1) res[i+j]=(res[i+j]+1ll*a[i]*b[j])%P;
	return res;
}
Poly operator + (Poly a,Poly b) {
	if(a.size()<b.size()) swap(a,b);
	rep(i,0,b.size()-1) a[i]+=b[i],Mod1(a[i]);
	return a;
}
ll Calc(Poly a,int x){ 
	ll res=0;
	for(int i=0,t=1;i<(int)a.size();++i,t=1ll*t*x%P) res=(res+1ll*t*a[i])%P;
	return res;
}

int a[N],b[N];
Poly sik[N],T[N*100];
// sik: sum i^k前缀和系数
int h[N*100],hc;

struct Node{ 
	int L,R,S; Poly V;
	bool operator < (const Node __) const { return L<__.L; }
}Empty;
vector <Node> dp[N][N]; // 存分段的dp函数

Poly PrefixSum(Poly V) {
	Poly res(V.size()+1);
	rep(i,0,V.size()-1) if(V[i]) rep(j,0,sik[i].size()-1) res[j]=(res[j]+1ll*V[i]*sik[i][j])%P;
	return res;
} // 将多项式转化为前缀和

Poly Que(vector <Node> &V,int x){ 
	if(V.rbegin()->R<=x){ Poly temp; temp.pb(V.rbegin()->S); return temp;}
	if(V[0].L>x) { Poly temp(1); return temp; }
	Empty.L=x;
	int p=upper_bound(V.begin(),V.end(),Empty)-V.begin()-1;
	if(V[p].L<=x && V[p].R>x) return V[p].V;
	else { Poly temp(1); return temp; }
} // 找到x对应的多项式分段

int C[N][N];
// x -> x-1
Poly Down(Poly x) {
	rep(i,0,x.size()-1) {
		rep(j,0,i-1) { 
			if((i-j)&1) x[j]-=1ll*x[i]*C[i][j]%P,Mod2(x[j]);
			else x[j]+=1ll*x[i]*C[i][j]%P,Mod1(x[j]);
		}
	}
	return x;
} // 由于右边是-1的前缀和,所以要把x-> x-1,直接二项式定理展开即可

void dfs(int L,int R) {
	if(dp[L][R].size()) return;
	if(L==R+1) {
		Poly tmp; tmp.pb(1);
		dp[L][R].pb((Node){0,1,1,tmp});
		return;
	}
	if(L==R) {
		int l=::L[L],r=::R[L];
		Poly tmp; tmp.pb(P-l+1); tmp.pb(1);
		dp[L][R].pb((Node){l,r,r-l,tmp});
		return;
	}
	int mid=(L+R)>>1;
	hc=0;
	rep(i,max(L,mid-2),min(R,mid+2)) if(abs((i-L)-(R-i))<=2) dfs(L,i-1),dfs(i+1,R);
	rep(i,max(L,mid-2),min(R,mid+2)) if(abs((i-L)-(R-i))<=2) {
		h[++hc]=::L[i],h[++hc]=::R[i];
		for(Node p:dp[L][i-1]) {
			h[++hc]=p.L;
			h[++hc]=p.R;
		}
		for(Node p:dp[i+1][R]) {
			h[++hc]=p.L+1;
			h[++hc]=p.R+1;
		}
	}
	sort(h+1,h+hc+1),hc=unique(h+1,h+hc+1)-h-1;
    // 预处理分段
	rep(i,1,hc) T[i].clear();
	rep(i,max(L,mid-2),min(R,mid+2)) 
        if(abs((i-L)-(R-i))<=2) 
            rep(j,1,hc-1) if(::L[i]<=h[j] && h[j]<::R[i]) 
            	T[j]=T[j]+Que(dp[L][i-1],h[j])*Down(Que(dp[i+1][R],h[j]-1));
    // 转移时相乘即可
	int lst=0;
	rep(i,1,hc-1) if(T[i].size()) {
		T[i]=PrefixSum(T[i]);
		T[i][0]-=Calc(T[i],h[i]-1),Mod2(T[i][0]);
		T[i][0]+=lst,Mod1(T[i][0]);
		lst=Calc(T[i],h[i+1]-1);
		while(T[i].size() && *T[i].rbegin()==0) T[i].pop_back();
		if(!T[i].size()) continue; // 小剪枝
		dp[L][R].pb((Node){h[i],h[i+1],lst,T[i]});
        // 累成前缀和
	}
	if(!dp[L][R].size()) {
		Poly temp(1);
		dp[L][R].pb((Node){0,1,0,temp});
	} // 如果出现dp值为空要特判
	return;
}


Poly Lang(int n,int *a,int *b){
	Poly res(n),c(n+1),tmp;
	c[0]=1;
	//  c = (x-a[i])
	rep(i,0,n-1) {
		drep(j,i+1,0) {
			c[j]=(-1ll*c[j]*a[i]%P+(j?c[j-1]:0)+P)%P;
		}
	}
	rep(i,0,n-1) {
		ll t=1;
		rep(j,0,n-1) if(i!=j) t=t*(a[i]-a[j])%P;
		t=(qpow(t)*b[i]%P+P)%P;
		tmp=c;
		// c/(x-a[i])
		drep(j,n-1,0) {
			res[j]=(res[j]+t*tmp[j+1])%P;
			tmp[j]=(tmp[j]+1ll*a[i]*tmp[j+1])%P;
		}
	}
	return res;
} // 拉格朗日插值

int main(){
	freopen("robot.in","r",stdin),freopen("robot.out","w",stdout);
	n=rd();
	rep(i,0,n+2) rep(j,C[i][0]=1,i) C[i][j]=(C[i-1][j-1]+C[i-1][j])%P; // 预处理组合数
	rep(i,0,n+2){
		ll s=0;
		rep(j,1,i+1) {
			s+=qpow(j,i),Mod1(s);
			a[j]=j,b[j]=s;
		}
		sik[i]=Lang(i+2,a,b);
	}
	rep(i,1,n) L[i]=rd(),R[i]=rd()+1;
	dfs(1,n);
	printf("%d\n",dp[1][n].rbegin()->S);
}



原文地址:https://www.cnblogs.com/chasedeath/p/12809374.html