Bzoj4361:isn:树状数组+动态规划+容斥

题目链接:isn

上来就觉得这不是个DP就是个数学题,结果他既是DP又是数学题QAQ

设f[i][j]表示以i结尾的长度为j的下降序列,则f[i][j]的计算式为

发现暴力计算这个是的,于是我们借助树状数组求逆序对的方法计算f[i][j],这样就优化到了

我们设g[i]表示长度为i的非下降子序列个数,则

最后统计答案时还要用到容斥原理。单纯考虑i贡献的答案是ans[i]=

但是可以发现我们枚举剩下(n-i)个数的删除顺序时可能已经在某个时刻形成了非降序列,按题意这时候应该停止但是我们并没有停止

在考虑我们如果枚举到了一个非法的删除序列,他一定是从i+1个数删除一个来的,我们枚举删的是哪个,减去这时候的序列个数

因为不管枚举到的这个序列合法非法,他一定被计算到了ans[i]中,所以这么做是对的

#include
#include
#include
#define LL long long
using namespace std;
const LL mod=1000000007;
const int maxn=2010;
int n,N,pos[maxn];
LL a[maxn],t[maxn],sum[maxn][maxn];
LL f[maxn][maxn],g[maxn],fac[maxn];

void init_hash(){
	for (int i=1;i<=n;++i) t[i]=a[i];
	sort(t+1,t+n+1);
	N=unique(t+1,t+n+1)-t-1;
}

int lowbit(int x){return x&-x;}

void update(int id,int x,LL v){
	for (int i=x;i<=n;i+=lowbit(i))
	    sum[id][i]=(sum[id][i]+v)%mod;
}

LL query(int id,int x){
	LL ret=0;
	for (int i=x;i;i-=lowbit(i))
		ret=(ret+sum[id][i])%mod;
	return ret;
}

int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%lld",&a[i]);
	init_hash(); fac[1]=1;
	for (int i=1;i<=n;++i)
	    pos[i]=lower_bound(t+1,t+N+1,a[i])-t;
	for (int i=2;i<=n;++i) fac[i]=1LL*fac[i-1]*i%mod;
	update(0,1,1);
	for (int i=1;i<=n;++i)
	    for (int j=i;j>=1;--j){
			f[i][j]=(f[i][j]+query(j-1,pos[i])%mod)%mod;
			update(j,pos[i],f[i][j]%mod);
	    }
	for (int i=1;i<=n;++i)
	    for (int j=i;j<=n;++j)
			g[i]=(g[i]+f[j][i])%mod;
	LL ans=0;
	for (int i=1;i<=n;++i)
	    ans=((ans%mod+(fac[n-i]%mod*g[i]%mod)%mod)%mod-fac[n-i-1]%mod*g[i+1]%mod*(i+1)%mod+mod)%mod;
	printf("%lld",ans);
}


你可能感兴趣的:(OI,动态规划,树状数组,容斥原理,OI,dp,树状数组,容斥原理,BZOJ)