【BZOJ 4361】isn(dp计数+容斥)

题目链接

题目描述

给出一个长度为n的序列A(A1,A2…AN)。如果序列A不是非降的,你必须从中删去一个数,
这一操作,直到A非降为止。求有多少种不同的操作方案,答案模10^9+7。

Sol

初看这题觉得很神仙啊 , 看到那个非降后立即停止就感觉不好做

所以我们要想着我们要知道些什么东西才能让问题变得好做起来

难点有2:
1.求出能够使得非降的操作序列个数
2.满足这些操作序列的合法性

对于第一个条件 我们可以发现 , 假设我们确定了要删什么元素能使得序列非降 , 那么就只用拿个阶乘计算操作的排列数就行了

所以我们考虑 dp 求解一个方案数,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i 个元素选出了一个长度为 j 的非降序列 , 其他的都是要删掉的的方案数

发现要求非降 , 我们必须知道最后一个选的数是什么 , 因此可以把状态加个限制 第 i 位必须选

显然我们可以写出一个 O ( n 3 ) O(n^3) O(n3)的 dp , 不过这个东西 离散化后 用树状数组优化再明显不过可以做到 O ( n 2 l o g n ) O(n^2log n) O(n2logn)

然后考虑怎么计算答案 , 考虑一个操作序列不合第二个条件的原因

显然是本来序列就已经非降了但是你又删掉了一个元素 , 这样的话当我们用长度为 j 的序列计算答案的时候不合法的操作序列最后一定从长度为 j+1 的不降序列中删去了一个元素
那么就可以直接容斥求出和法方案

直接把方案数 减去 d p [ i ] [ j + 1 ] ∗ ( n − j − 1 ) ! ∗ ( j + 1 ) dp[i][j+1]*(n-j-1)!*(j+1) dp[i][j+1](nj1)!(j+1) , 表示上一个长度删完后再从中删掉一个元素的方案,即为当前的不合法方案数

可能有人会疑惑为什么这里是 (j+1) , 不是说好了 i i i 必须要在不降序列里吗?

发现我们如果直接这样考虑其实是忽略了一种情况的 , 可能最后一个删掉的并不在前 i 个中间 , 而是在后面! 这样就没办法直接在统计 i 的时候减去贡献了 , 但是后面正因为是必须选择 i ,那么此时把 i 删掉就相当于把前面没有统计到的不合法方案也给减去了 , 真是一步妙棋

代码:

#include
#include
#include
#include
#include
#include
#include
using namespace std;
int n;
const int N=2e3+10,mod=1e9+7;
int a[N];
int f[N][N];
int fac[N];
int tr[N][N];
typedef long long ll;
template <typename T> inline void upd(T&x,int y){x+=y;if(x>=mod) x-=mod;return;}
template <typename T> inline void dec(T&x,int y){x-=y;if(x <  0) x+=mod;return;}
int st[N],top=0;
#define lowbit(a) ((a)&(-a))
inline void update(int *tr,int p,int x){for(;p<=top;upd(tr[p],x),p+=lowbit(p));return;}
inline int Query(int *tr,int p){int ret=0;for(;p;upd(ret,tr[p]),p-=lowbit(p));return ret;}
int main()
{
	ios::sync_with_stdio(false);
	cin>>n;bool fl=1;fac[0]=1;
	for(int i=1;i<=n;++i) {cin>>a[i];if(a[i]<a[i-1]) fl=0;fac[i]=1ll*fac[i-1]*i%mod;st[++top]=a[i];}
	if(fl) return puts("1"),0;
	sort(st+1,st+1+top);top=unique(st+1,st+1+top)-st-1;
	for(int i=1;i<=n;++i) a[i]=lower_bound(st+1,st+1+top,a[i])-st;
	for(int i=1;i<=n;++i){
		f[i][1]=1;
		for(int j=i;j>=2;--j){
			f[i][j]=Query(tr[j-1],a[i]);
			update(tr[j],a[i],f[i][j]);
		}update(tr[1],a[i],1);
	}
	int ans=0;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=i;++j) {
			upd(ans,1ll*f[i][j]*fac[n-j]%mod);
			if(j<i) dec(ans,1ll*f[i][j+1]*fac[n-j-1]%mod*(j+1)%mod);
		}
		
	}
	printf("%d\n",ans);
}

你可能感兴趣的:(======题解======,——动态规划——,计数问题,容斥原理)