BZOJ3622: 已经没有什么好害怕的了 解题报告

138 篇文章 0 订阅
11 篇文章 0 订阅

感觉做完这题已经没有什么题害怕了呢(算了当我没说)
首先安利一个写的很好的题解orz
http://www.cnblogs.com/dyllalala/p/3900077.html
写的挺详细的了,但是我总要写点什么吧,于是再解释一下做法…….

首先可以把题意转化一下,如果 (n+k)mod2=1 那肯定是没有满足要求的情况了,如果不为1,问题就是要求糖果大于药片的恰好有 m=(n+k)/2
a代表糖果,b代表药片
那么 (看题解) 不考虑要恰好有m组,只考虑至少m组易得一个DP:
先排一个序,然后处理一个数组pos,pos[i]表示最大的j使得 a[i]>b[j]
f[i][j] 为a前i个数和b数组匹配,保证有至少j组a大于b(可能大于j组)

f[i][j]=f[i1][j]+f[i1][j1](pos[i](j1))

f[i1][j] 比较好理解, f[i1][j1](pos[i](j1)) 的话,因为 i1 匹配了 j1 个,所以 ai 这个位置只有 (pos[i](j1)) 个数可选
现在得到了f数组,但我们不能将 f[n][m] 直接作为答案,因为我们只能保证a数组有m个数和b数组里的m个数匹配,但不知道剩下的数的匹配情况,所以这里用容斥去重
g[i] 表示恰好有i组a大于b
g[i]=f[n][i](ni)!g[j]Cij,(i<j<=n)

因为不知道a里面剩下的n-i个数是怎么匹配的,但是一定有 (ni)! 种匹配情况,这些情况里包含了恰好有j组a大于b的情况 (i<j) ,j组被计算到的次数是 Cij 种,所以减去, g[m] 即是答案



#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const ll Mod=1e9+9;
const int maxn = 2100;
int pos[maxn],n,m;
ll a[maxn],b[maxn];
ll f[maxn][maxn],c[maxn][maxn],N[maxn],p[maxn];
ll g[maxn];

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(a==0)
    {
        x=0;y=1;
        return b;
    }
    ll tx,ty;
    ll d=exgcd(b%a,a,tx,ty);
    x=ty-b/a*tx;
    y=tx;
    return d;
}

int main()
{   
    scanf("%d%d",&n,&m);
    if((n-m)&1){printf("0\n");return 0;}
    m=(n+m)>>1;
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++)scanf("%lld",&b[i]);
    sort(a+1,a+n+1);
    sort(b+1,b+n+1);

    for(int i=1;i<=n;i++)
    {
        int p=0;
        while(p<n&&b[p+1]<a[i])p++;
        pos[i]=p;
    }

    ll tmp;
    for(int i=1;i<=n;i++)
    {
        exgcd(i,Mod,N[i],tmp);
        N[i]=((N[i]%Mod)+Mod)%Mod;
    }
    p[0]=1;
    for(int i=1;i<=n;i++)
    {
        tmp=1;p[i]=1;
        for(int j=1;j<=i;j++)
        {
            tmp=(tmp*(ll)(i-j+1))%Mod;
            tmp=(tmp*N[j])%Mod;
            c[i][j]=tmp;
            p[i]=(p[i]*(ll)j)%Mod;
        }
    }

    f[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(pos[i]>=j)
                f[i][j]=(f[i-1][j]+f[i-1][j-1]*(pos[i]-j+1))%Mod;
            else f[i][j]=0;
        }
    }

    for(int i=n;i>=m;i--)
    {
        g[i]=f[n][i]*p[n-i];
        for(int j=i+1;j<=n;j++)
        {
            g[i]=((g[i]-g[j]*c[j][i])%Mod+Mod)%Mod;
        }
    }
    printf("%lld\n",g[m]);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值