AGC001 E - BBQ Hard(思维+组合数学+dp)

题意:

在这里插入图片描述

解法:
a[i]和b[i]很小,是可以O(n^2)的范围

C(x+y,x)可以看作是(0,0)(x,y)的方案数,
如果我们要求(0,0)(x,y)的方案数,
可以令d[i][j]表示(0,0)(i,j)的方案数,一开始d[0][0]=1.
转移方程为d[i][j]=d[i-1][j]+d[i][j-1].

C(a[i]+a[j]+b[i]+b[j],a[i]+a[j])可以看作:
(0,0)(a[i]+a[j],b[i]+b[j])的方案数.

枚举i和j是O(n^2),显然要优化,
尝试将i和j分开:
将坐标位移一下变为(-a[i],-b[i])(a[j],b[j])的方案数.
这样i和j就分离了,然后就可以单独计算每个部分的贡献.

观察前面的dp方程,我们一开始令d[0][0]=1,
然后计算出的d[x][y]就是(0,0)(x,y)的方案数,
那么我们令d[-a[i]][-b[i]]=1,
那么计算出的d[x][y]就是(-a[i],-b[i])(x,y)的方案数.
这样的话,我们一开始只需要令所有的d[-a[i]][-b[i]]++,
那么计算出来的d[x][y]就是所有数对到达(x,y)的方案数.
因此最后只需要累加d[a[i]][b[i]]即可.
但是存在(-a[i],-b[i])(a[i],b[i])的方案数,也就是自己和自己匹配,
而题目只需要计算i<j的,因此减掉自己和自己匹配的,方案数为C(2*(a[i]+b[i]),2*a[i]).
最后答案还要再除以2,因为是计算出的是无序的,题目要的是有序的.

参考:
https://www.luogu.com.cn/blog/Ebola-Emperor/solution-at1983
https://www.luogu.com.cn/blog/208529/solution-at1983
code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=2e5+5;
const int mod=1e9+7;
int fac[maxm],inv[maxm];
int a[maxm],b[maxm];
int d[4444][4444];
int n;
int ppow(int a,int b,int mod){
    int ans=1%mod;a%=mod;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
void init(){
    fac[0]=1;
    for(int i=1;i<maxm;i++)fac[i]=fac[i-1]*i%mod;
    inv[maxm-1]=ppow(fac[maxm-1],mod-2,mod);
    for(int i=maxm-2;i>=0;i--)inv[i]=(i+1)*inv[i+1]%mod;
}
int C(int n,int m){
    if(m<0||m>n)return 0;
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main(){
    init();
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
    const int base=2000;
    for(int i=1;i<=n;i++){
        d[-a[i]+base][-b[i]+base]++;
    }
    for(int i=0;i<=4010;i++){
        for(int j=0;j<=4010;j++){
            if(i-1>=0)d[i][j]+=d[i-1][j];
            if(j-1>=0)d[i][j]+=d[i][j-1];
            d[i][j]%=mod;
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        ans+=d[a[i]+base][b[i]+base];
        ans%=mod;
    }
    for(int i=1;i<=n;i++){
        ans-=C(2*(a[i]+b[i]),2*a[i]);
        ans%=mod;
    }
    ans=(ans*ppow(2,mod-2,mod)%mod+mod)%mod;
    cout<<ans<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值