BZOJ3622: 已经没有什么好害怕的了 DP

来源:互联网 发布:专业书籍网络图书馆 编辑:程序博客网 时间:2024/04/28 18:45

题意:有N个糖果,N个药片,每个糖果和药片都有权值,求所有的两两配对方案中,糖 > 药的组数比药 >糖的组数多k组的方案数。n<=2000,保证所有权值不相同。
DP好题。
将问题转化为求糖 > 药的组数正好(n+k)/2组的方案数,若不是整数则显然为0。由于与顺序无关,所以可以先对两组数据排序。
接下来考虑如何设计状态。
排完序后,容易预处理出每一个糖果大于多少个药片(记为cnt[i])。设f[i][j] 为枚举到第i个糖果,糖 > 药有j组的方案数。转移:不使用第i个糖果,有f[i-1][j] 组;使用第i个糖果,先用前i-1个糖果配出j-1组,第i个糖果还能配对的有cnt[i]-j+1个。如果这个数是负数那么显然是不可行的。所以f[i][j] =f[i-1][j]+f[i-1][j-1]*max(cnt[i]-j+1,0)。
这样我们得到了“从n个糖果中选出k个,从n个药片中选出k个,配成k组糖 > 药的方案数“。但是由于我们没有考虑未被选出的糖和药以何种方式配对,所以这并不是糖 > 药恰好k对的方案数。我们先将得到的f[n][x] 乘以A(n-x,n-x),代表让没被选中的糖、药全排列,再考虑其中包含了什么样的重复元素。
从这里开始,已经用不到中间状态,只需考虑f[n][x](乘以全排列后的),所以将f[n][x]记为g[x]。
举个例子,比如现有一种糖 > 药正好为5组的既定方案,那么这种方案中任取三个糖 > 药的对子,都被作为”取3个糖 >药的配对“而在g[3]中出现过一次。
可以抽象理解一下:一个组数多于x的方案,就像一个高维物体,用x组的眼光去看它,看到的每一个组数为x的子集都像这个物体在x维的一个不同方面的投影。对于一个y组的方案,会产生C(y,x)个投影。那么将这些投影减去,就可以得到”正好x组“的方案数。
因此可以倒着求,求到x的时候,g[x+1]、g[x+2]…g[n]都已经是“正好”的方案数了,于是就可以得到答案了。
由于我智商有限,网上各路神犇的题解理解起来实在是绞尽脑汁,希望我的这篇文章能更加通俗生动地帮助有同样境遇的同学吧,写得不好或者不严谨请见谅。
组合数忘取模WA了3次233333

#include<cstdio>#include<algorithm>using namespace std;typedef long long ll;const ll mob=1000000009;ll f[2001][2001],C[2001][2001],ans[2001],suf[2001]={1};int a[2001],b[2001],cnt[2001];int n,k;int main(){    scanf("%d%d",&n,&k);    if((n+k)&1) return puts("0"),0;    k=n+k>>1;    for(int i=1;i<=n;++i) scanf("%d",a+i);sort(a+1,a+n+1);    for(int i=1;i<=n;++i) scanf("%d",b+i);sort(b+1,b+n+1);    for(int i=1,now=1;i<=n;++i)    {        while(now<=n&&b[now]<a[i]) ++now;        cnt[i]=now-1;    }    f[0][0]=1;    for(int i=1;i<=n;++i)    {        f[i][0]=1;        for(int j=1;j<=n&&f[i-1][j-1];++j)        f[i][j]=(f[i-1][j]+f[i-1][j-1]*(j<=cnt[i]?cnt[i]-j+1:0))%mob;    }    for(int i=0;i<=n;++i)    {        C[i][0]=1;        for(int j=1;j<=i;++j)        C[i][j]=(C[i-1][j]+C[i-1][j-1])%mob;    }    for(int i=1;i<=n;++i)        suf[i]=suf[i-1]*i%mob;    for(int i=n;i>=k;--i)    {        ans[i]=f[n][i]*suf[n-i]%mob;        if(!ans[i]) continue;        for(int j=i+1;j<=n;++j)        {            ans[i]-=ans[j]*C[j][i];            if(ans[i]<0) ans[i]=ans[i]%mob+mob;        }    }    printf("%lld\n",ans[k]%mob);    return 0;}
0 0
原创粉丝点击