题解「BZOJ4621 Tc605」

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

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

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

由于单点不一定要被覆盖,所以会出现用 (0sim 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;
}
原文地址:https://www.cnblogs.com/tommy0103/p/13842176.html