魔改森林

链接:https://ac.nowcoder.com/acm/contest/4474/A
来源:牛客网
 

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

曾经有一道叫做迷雾森林的题目,然而牛牛认为地图中的障碍太多,实在是太难了,所以删去了很多点,出了这道题。

 

牛牛给出了一个n行m列的网格图

初始牛牛处在最左下角的格点上(n+1,1),终点在右上角的格点(1,m+1)

现在它想知道,从起点走到终点,只能向上或向右走,一共有多少种走法呢?

需要注意的是,除了起点和终点外,其它的每个格点都有可能有障碍,无法通过。

请注意格子与格点的区别

输入描述:

第一行三个整数n,m,k,表示格子的大小,以及有k个障碍
接下来k行(若k=0则无此行),每行一个格点坐标(x,y),表示每个障碍的位置

输出描述:

仅一行,一个整数表示答案对998244353取模的值

示例1

输入

复制4 5 1 3 4

4 5 1
3 4

输出

复制66

66

说明

 

示例2

输入

复制12345 54321 2 123 321 456 654

12345 54321 2
123 321
456 654

输出

复制801071140

801071140

示例3

输入

复制114 514 3 19 19 8 10 65 45

114 514 3
19 19
8 10
65 45

输出

复制567102428

567102428

备注:

 

比较无聊就去牛客打了场oi,然后发现不太会

只写了个暴力的想法,赛后倒是看到了我这个暴力想法的后续,不得不说,大佬nb

但其实这个题可以容斥orz

有一个差不多和偏序关系相似的地方

假设只有一个点不能走的话,那么用总的(C(n+m-2,n-1))。减掉经过这个点(C(x+y-2,x-1)*C(n-x+n-y,n-x))的即可

即C(n+m-2,n-1)-C(x+y-2,x-1)*C(n-x+n-y,n-x)

如果有多个个点的话容斥。

根据位置,如果存在一个偏序关系的话,那么是可以叠加的。

就是说起点->A->B->.....->终点,这个样子,如图

如果不存在偏序关系,那么这次的选择自然是不被计算的。

#include <bits/stdc++.h>
/*author:revolIA*/
/*明日を変えるなら今日変えなきゃ*/
using namespace std;
const int mod = 998244353;
const int N = 1e6+7;
int n,m,k;
int mmp[N];
void solve1(){
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            mmp[i*(m+1)+j] = 0;
        }
    }
    mmp[0] = 1;
    while(k--){
        int x,y;
        scanf("%d%d",&x,&y);
        --x,--y;
        x = n-x;
        mmp[x*(m+1)+y] = -1;
    }
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++)if(~mmp[i*(m+1)+j]){
            if(i && ~mmp[(i-1)*(m+1)+j])mmp[i*(m+1)+j] = (mmp[(i-1)*(m+1)+j]+mmp[i*(m+1)+j])%mod;
            if(j && ~mmp[i*(m+1)+j-1])mmp[i*(m+1)+j] = (mmp[i*(m+1)+j-1]+mmp[i*(m+1)+j])%mod;
        }
    }
    printf("%d\n",mmp[(n+1)*(m+1)-1]);
}
int F[N] = {1},inv[N];
int Pow(int a,int b,int ans = 1){
    for(a%=mod;b;b>>=1,a=1LL*a*a%mod)if(b&1)ans=1LL*ans*a%mod;
    return ans;
}
int C(int n,int m){
    if(n<0||m<0||n-m<0)return 0;
    return (int)(1LL*F[n]*inv[n-m]%mod*inv[m]%mod);
}
void  solve2(){
    pair<int,int> p[15];
    for(int i=0;i<k;i++){
        scanf("%d%d",&p[i].first,&p[i].second);
        --p[i].first,--p[i].second;
        p[i].first = n-p[i].first;
    }
    sort(p,p+k);
    int ans = C(n+m,n);
    for(int s=1;s<(1<<k);s++){
        int lasx = 0,lasy = 0,num = 0,Tmp = 1;
        for(int i=0;i<k;i++)if((1<<i)&s){
            num++;
            if(lasx > p[i].first || lasy > p[i].second)Tmp = 0;
            else Tmp = 1LL*Tmp*C(p[i].first+p[i].second-lasx-lasy,p[i].first-lasx)%mod;
            lasx = p[i].first,lasy = p[i].second;
        }
        Tmp = 1LL*Tmp*C(n+m-lasx-lasy,n-lasx)%mod;
        if(num&1)ans = (1LL*ans-Tmp+mod)%mod;
        else ans += Tmp,ans %= mod;
    }
    printf("%d\n",ans);
}
int main(){
    for(int i=1;i<N;i++)F[i] = (1LL*i*F[i-1])%mod;
    inv[N-1] = Pow(F[N-1],mod-2);
    for(int i=N-2;~i;i--)inv[i] = (1LL*(i+1)*inv[i+1])%mod;
    scanf("%d%d%d",&n,&m,&k);
    (1LL*n*m < 1000000LL)?solve1():solve2();
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值