题解「BZOJ4621 Tc605」

可以发现,每次操作事实上是一次推平。而取最大值的限制则限定了每个值能够推平的最大区间,记 \(a_i\) 能够推平到最左的位置为 \(l_i\),能够推平到最右的位置为 \(r_i\)

那么问题转化为用 \(m\) 个区间覆盖整个序列,但可以有长度为 \(1\) 的段不被覆盖(其实就是单点)

\(f_{i,j}\) 为使用了 \(j\) 个区间覆盖 \(1\sim i\),直接转移不太方便,刷表转移即可。枚举每个 \(i\),对于所有的 \(l_i\leq k\leq r_i\),将 \(f_{k,j}\) 加上 \(\sum\limits_{x=l_i-1}^{k-1} f_{k,j-1}\)。直接转移总时间复杂度是 \(O(n^4)\) 的,观察到右端点 \(k-1\) 单调增,前缀和优化即可。另外,值得注意的是,单点的情况会被覆盖两次,需要减去一次。

由于单点不一定要被覆盖,所以会出现用 \(0\sim m\) 段区间覆盖整个序列的情况,都要统计进答案内。

#include<cstdio>
const int mod=1e9+7;
int f[505][505],a[505];
inline int read() {
	register int x=0,f=1;register char s=getchar();
	while(s>'9'||s<'0') {if(s=='-') f=-1;s=getchar();}
	while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
	return x*f;
}
int main() {
	int n=read(),m=read(),ans=0;
	for(register int i=1;i<=n;++i) a[i]=read(); f[0][0]=1;
	for(register int i=1;i<=n;++i) {
		int L=i-1,R=i+1; while(L>=1&&a[L]<a[i]) --L; while(R<=n&&a[R]<a[i]) ++R;
		for(register int j=m;j>=0;--j) {
			f[i][j]=(f[i][j]+f[i-1][j])%mod;
			if(j) {
				for(register int k=L+1,sum=0;k<=R-1;++k) {sum=(sum+f[k-1][j-1])%mod; f[k][j]=(f[k][j]+sum)%mod;}
				f[i][j]=(f[i][j]-f[i-1][j-1]+mod)%mod;
			}
		}
	}
	for(register int i=0;i<=m;++i) ans=(ans+f[n][i])%mod; printf("%d",ans);
	return 0;
}
posted @ 2020-10-19 19:20  tommymio  阅读(79)  评论(0)    收藏  举报