noip模拟赛 小球游戏 cqbzoj3391(网格路径模型)

哇,考试考数学真的是不能玩了,特别还是考这种极其恶心的

题意因为版权原因不能放上来,不过可以告诉你答案要mod 998244353

。。。


题解:(趁我现在还记得,赶紧写下来。。。)

首先说一下期望的概念:

这道题要求的是方案的期望

那么E=sigma(当前方案出现的概率*当前方案的方案数)

概率在这里很显然是1/C(n,2),那么要求最后答案也要乘上C(n,2)

就抵消了,所以实际要求的是方案的总数


来考虑选择第i个盒子和第j个盒子时的方案数

因为总共只有两种小球,那么方案数就等于C(a[i]+a[j]+b[i]+b[j],a[i]+a[j])

即在要放成一排的位置中选出白(红)球要放的位置


于是,直接暴力求解的方案就出来了,枚举所有的方案,求和。

时间复杂度O(n^2),直接爆炸(考试的时候这种垃圾暴力都写错了。。。)


正解是将小球问题转换成网络路径问题

对于一个n*m的网格图,从(0,0)走到(n,m)的方案数是C(n+m,n)

这是可以直接得出来的,但是也可以通过dp的方式得出

dp[i][j]=dp[i][j-1]+dp[i-1][j]

不难看出,这个dp是可以叠加起来做的

举个例子:

在用dp计算(0,0)到(n,m)的方案数时,并不妨碍我们顺便求出

(1,1)到(n-1,m-1)的值,只需要在dp[1][1]++,就可以了


但仍然不能搞,还要引入负的坐标

那么对于这道题读入一个小球时,就在dp[-a[i]][-b[i]]++

那么第i个盒子和第j个盒子所构成的方案数就是

在坐标轴上(-a[i],-b[i])到(a[j],b[j])的dp值

坐标为负的情况加一个3000就可以了

完美解决了需要枚举的问题,巧妙利用了之前求出的值Orz


最后要去重,这种弄在一起搞dp,会出现dp[-a[i]][-b[i]]到dp[a[i]][b[i]]

的情况,要把这种情况减去,还有就是在已经选了i和j后,还会再选一次j和i

这个把最后的答案除以2,也就是乘2的逆元就可以了


这样,就可以做了,还有一些细节见代码

code:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>

const int MAXC=12000;
const int MAXA=6000;
const int mod=998244353;
typedef long long LL;
using namespace std;
int jc[MAXC+10],rjc[MAXC+10];
int dp[MAXA+10][MAXA+10];
int cnt[3010][3010];
int n,a,b,sum,ans;

inline void prepare(){
    jc[0]=jc[1]=rjc[0]=rjc[1]=1;
    for(int i=2;i<=MAXC;i++)
    {
        jc[i]=1ll*jc[i-1]*i%mod;
        rjc[i]=1ll*(mod-mod/i)*rjc[mod%i]%mod;
    }
    for(int i=2;i<=MAXC;i++)
        rjc[i]=1ll*rjc[i]*rjc[i-1]%mod;
}
inline int C(int n,int k){
    return 1ll*jc[n]*rjc[k]%mod*rjc[n-k]%mod;
}
inline int add(int a,int b){
    return a+b>mod?a+b-mod:a+b;
}
inline void Read(int &Ret){
    char ch;bool flag=0;
    for(;ch=getchar(),!isdigit(ch);)if(ch=='-')flag=1;
    for(Ret=ch-'0';ch=getchar(),isdigit(ch);Ret=Ret*10+ch-'0');
    flag&&(Ret=-Ret);
}
int main()
{
    Read(n); prepare();
    for(int i=1;i<=n;i++)
    {
        Read(a); Read(b);
        sum=add(sum,C((a+b)<<1,a<<1));
        dp[-a+3000][-b+3000]++;
        cnt[a][b]++;
    }
    for(int i=0;i<=MAXA;i++)
        for(int j=0;j<=MAXA;j++)
        {
            if(i) dp[i][j]=add(dp[i][j],dp[i-1][j]);
            if(j) dp[i][j]=add(dp[i][j],dp[i][j-1]);
            if(i>=3000&&j>=3000&&cnt[i-3000][j-3000])
                ans=add(ans,1ll*dp[i][j]*cnt[i-3000][j-3000]%mod);
        }
    ans=add(mod,ans-sum);
    ans=1ll*ans*rjc[2]%mod;
    printf("%d\n",ans);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值