bzoj2302-Problem c

题意

\(n\) 个人,从 1 到 \(i\) 编号。给每个人一个值 \(a_i\) ,他们会按编号从小到大进行如下操作:查看 \(a_i\) 有没有人,若没有就坐进去,否则查看 \(a_i+1\) ……

按照这个方法,若一个人没地方坐,那么这个方案不合法。现在给定一部分人的 \(a_i\) ,对剩下的人有多少种分配 \(a\) 的合法方案。\(n\le 300\)

分析

可以看 这篇题解

对奇怪的问题,要找出简单的等价形式

观察一下这个摆放方法,可以发现,若标号大于等于 \(x\) 的个数大于 \(n-x+1\) ,那么一定是不可行的,因为每个人只会往后走。更好看的形式是,若标号小于等于 \(x\) 的个数大于等于 \(x\) ,那么这个方案可行。

必要性显然。下面说明充分性。设满足上述条件的情况下 \(x\) 没有地方坐,那就说明值大于等于 \(a_x\) 的至少有 \(n-a_x+2\) 个,与满足上述要求矛盾。因此标号小于等于 \(x\) 的个数大于等于 \(x\) 等价于有合法解。

那么我们dp这个东西就行啦!

\(f[i][j]\) 表示有 \(j\) 个人的值在 \([1,i]\) 中的方案数,显然有转移

\[f[i][j]=\sum _{0\le k\le j}f[i-1][k]\binom {n-k} {j-k} \]

考虑上已经钦定的人,就是将总可选人数减少,要求改为 \(i\) 之前的至少有 \(j-p_i\) 个,其中 \(p_i\) 为钦定选 \(i\) 之前的个数前缀和。

复杂度为 \(O(n^3)\)

代码

#include<bits/stdc++.h>
#define M(x) memset(x,0,sizeof x)
using namespace std;
typedef long long giant;
inline int read() {
	int x=0,f=1;
	char c=getchar();
	for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
	for (;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x*f;
}
const int maxn=301;
int n,m,b[maxn],f[maxn][maxn],q,c[maxn][maxn],suf[maxn];
inline int Plus(int x,int y) {return ((giant)x+(giant)y)%q;}
inline void Pe(int &x,int y) {x=Plus(x,y);}
inline int Multi(int x,int y) {return (giant)x*y%q;}
inline void work() {
	n=read(),m=read(),q=read();
	M(f),M(b),M(c),M(suf);
	c[0][0]=1;
	for (int i=1;i<maxn;++i) for (int j=c[i][0]=1;j<=i;++j) c[i][j]=Plus(c[i-1][j],c[i-1][j-1]);
	for (int i=1;i<=m;++i) read(),++b[read()];
	for (int i=n;i;--i) if ((suf[i]=suf[i+1]+b[i])>n-i+1) {puts("NO");return;}
	for (int i=1;i<=n;++i) b[i]+=b[i-1];
	f[0][0]=1;
	for (int i=1;i<=n;++i) {
		for (int j=0;j<=n;++j) for (int k=0;k<=j;++k) Pe(f[i][j],Multi(f[i-1][k],c[n-m-k][j-k]));
		for (int j=0;j<i-b[i];++j) f[i][j]=0;
	}
	printf("YES %d\n",f[n][n-m]);
}
int main() {
#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
#endif
	for (int T=read();T--;) work();
	return 0;
}
posted @ 2017-09-18 21:31  permui  阅读(197)  评论(0编辑  收藏  举报