【NOI2019】机器人
题面
https://www.luogu.org/problem/P5469
题解
这里,我们只考虑$50pts$的区间$dp$做法。
因为最大值把区间劈成互不影响的两端,我们可以用类似分治的思想设计$dp$,解决这个问题。
设$f[l][r][x]$为$[l..r]$的最大值为$x$的方案数,转移的时候直接枚举最大值的位置,让左边的最大值小于等于它,让右边的最大值小于它,即可转移。
注意这类转移和合并石子不同,并不是两个区间的并,而是左区间并最大值并右区间,这样才能充分利用性质。
因为对于一个区间,可以取得最大值的位置是很少的,所以用记忆化的搜索优化(这更印证了分治思想)更加优美,可以做到更优秀的复杂度。但是我懒得写了。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ri register int #define N 55 #define H 105 #define LL long long #define mod 1000000007 using namespace std; inline int read() { int ret=0,f=0; char ch=getchar(); while (ch<'0' || ch>'9') f|=(ch=='-'),ch=getchar(); while (ch>='0' && ch<='9') ret*=10,ret+=ch-'0',ch=getchar(); return f?-ret:ret; } int n,ans=0; int c[N],a[N],b[N]; int f[N][N][H]; int mul(int x,int y) { LL z=x; z*=y; return (int)(z%mod); } int jia(int x,int y) { x+=y; if (x>=mod) x-=mod; return x; } int main() { n=read(); int mb=0; for (ri i=1;i<=n;i++) a[i]=read(),b[i]=read(),mb=max(mb,b[i]); for (ri i=0;i<=n;i++) f[i+1][i][0]=1; for (ri i=1;i<=n;i++) { for (ri j=a[i];j<=b[i];j++) f[i][i][j]=1; } for (ri l=1;l<=n-1;l++) for (ri lb=1,rb;lb+l<=n;lb++) { rb=lb+l; int pl,pr; if ((lb+rb)%2==1) { pl=(lb+rb)/2; pr=(lb+rb)/2+1; } else { pl=(lb+rb)/2-1; pr=(lb+rb)/2+1; } for (ri x=pl;x<=pr;x++) { for (ri h=a[x];h<=b[x];h++) { int s1=0; for (ri j=0;j<=h;j++) s1=jia(s1,f[lb][x-1][j]); int s2=0; for (ri j=0;j<h;j++) s2=jia(s2,f[x+1][rb][j]); f[lb][rb][h]=jia(f[lb][rb][h],mul(s1,s2)); } } } int ans=0; for (ri i=0;i<=mb;i++) ans=jia(ans,f[1][n][i]); cout<<ans<<endl; }