bzoj2302-Problem c

题意

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

按照这个方法,若一个人没地方坐,那么这个方案不合法。现在给定一部分人的 (a_i) ,对剩下的人有多少种分配 (a) 的合法方案。(nle 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 _{0le kle j}f[i-1][k]inom {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
",f[n][n-m]);
}
int main() {
#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
#endif
	for (int T=read();T--;) work();
	return 0;
}
原文地址:https://www.cnblogs.com/owenyu/p/7545359.html