Luogu P4859「已经没有什么好害怕的了」


以前开过一遍这题,以为很难没刚下去

今天$ review$一遍分析了一下感觉也还好

luogu 4859

题意:给定长度为$ n leq 2000$的数组$ A,B$求完全匹配使得$A>B$的对数比$A<B$的对数恰好多$k$组的方案数


$ Solution:$

直接$DP $是$ n^3$的

考虑容斥 先将$ A,B$从小到大排序

设$ F_{i,j}$表示只考虑$ A$的前$ i$个物品,进行了$ j$次匹配均满足$ A>B$的方案数

显然每次$ A$能转移的是$B$的一段前缀区间,且$ i$前面匹配到的$B$一定在当前前缀区间内

有转移式:

$F_{i,j}=F_{i-1,j}+(now_i-j+1)·F_{i-1,j-1}$

其中$ now_i$表示最靠右的比$ A_i$小的$B$数组的位置

令$ g_i=F_{n,i}*(n-i)!$,则$ g_i$表示有至少$ i$对$ A>B$的对数的方案数

令$ f_i$表示有恰好$ i$对$ A>B$的对数的方案数

则根据定义有

$ g_i=sumlimits_{j=i}^n inom{j}{i}f_j$

二项式反演得

$ f_i=sumlimits_{j=i}^n(-1)^{j-i} inom{j}{i}g_j$

时间复杂度$ O(n^2)$


$ my code$

#include<ctime>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define p 1000000009
#define rt register int
#define ll long long
using namespace std;
inline ll read(){
    ll x = 0; char zf = 1; char ch = getchar();
    while (ch != '-' && !isdigit(ch)) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}
void write(ll y){if(y<0)putchar('-'),y=-y;if(y>9)write(y/10);putchar(y%10+48);}
void writeln(const ll y){write(y);putchar('
');}
int i,j,k,m,n,x,y,z,cnt;
int a[2010],b[2010],inv[2010],g[2010];
int main(){
    n=read();m=read();
    inv[0]=inv[1]=1;
    for(rt i=2;i<=n;i++)inv[i]=1ll*inv[p%i]*(p-p/i)%p;
    if(n+m&1)return write(0),0;
    for(rt i=1;i<=n;i++)a[i]=read();
    for(rt i=1;i<=n;i++)b[i]=read();
    sort(a+1,a+n+1);sort(b+1,b+n+1);
    int R=0;g[0]=1;
    for(rt i=1;i<=n;i++){
        while(a[i]>b[R+1]&&R<n)R++;
        for(rt j=i;j>=1;j--)
        (g[j]+=1ll*g[j-1]*(R-j+1)%p)%=p;
    }
    for(rt i=1,sum=1;i<=n;i++){
        sum=1ll*sum*i%p;
        g[n-i]=1ll*g[n-i]*sum%p;
    }
    m=(n+m)/2;int ans=0;
    for(rt i=m,tag=1,C=1;i<=n;i++,tag*=-1){
        (ans+=1ll*g[i]*C*tag%p)%=p;
        C=1ll*C*(i+1)%p*inv[i-m+1]%p;
    }
    cout<<(ans+p)%p;
    return 0;
}
原文地址:https://www.cnblogs.com/DreamlessDreams/p/10062604.html