2021牛客多校#10 K-Walking(DP,容斥)

本文探讨了一种n×m大小的迷宫路径计数问题,其中给出了两种解决方案:动态规划和全局考虑。动态规划适用于n较小的情况,复杂度为O(nt);全局考虑适用于n较大,复杂度为O(T^2/n)。文章详细解释了两种方法的思路,并提供了参考代码。
摘要由CSDN通过智能技术生成

题目链接

https://ac.nowcoder.com/acm/contest/11261/K

题目大意

有一个 n × m ( 1 ≤ n , m ≤ 5 × 1 0 5 ) n \times m(1\leq n,m\leq 5\times 10^5) n×m(1n,m5×105)大小的迷宫,两角坐标为 ( 1 , 1 ) (1,1) (1,1) ( n , m ) (n,m) (n,m)
若你当前在 ( x , y ) (x,y) (x,y) 位置,每秒你可以向上下左右方向移动一格,不能停留在原地,不能移动到迷宫外。
初始在 a , b a,b a,b 处,继续移动 t ( 1 ≤ t ≤ 5 × 1 0 5 ) t(1\leq t\leq 5\times 10^5) t(1t5×105)

题解

这道题要求算出合法路径数,每一步只会受到前一步的影响,所以我们用DP来转移方案数。
一般来说定义的DP式子定义为 d p i , j dp_{i,j} dpi,j表示在 ( i , j ) (i,j) (i,j)位置上的方案,显而易见,复杂度是 O ( n 3 ) O(n^3) O(n3)级别的,很不幸,无法实现,故我们想办法压缩维度。
由于它只能移动到相邻的点,所以我们可以更改DP的设定,将两个维度拆开处理。设 x i x_i xi为纵向走 i i i 步不超出的方案数, y i y_i yi为横向走 i i i 步不超出的方案数,则原问题可变为求解 ∑ i = 0 t x i ∗ y i ∗ C i t \sum^{t}_{i=0}x_i*y_i*C_{i}^{t} i=0txiyiCit C i t C_{i}^{t} Cit表示从 t t t 步中选取 i i i 步)。

①显然可以用动态规划解决。
d p i , j dp_{i,j} dpi,j为走了 i i i 步,在 j j j 的方案数。
易得 d p 0 , j = 0 dp_{0,j}=0 dp0,j=0 d p i , j = d p i − 1 , j − 1 + d p i − 1 , j + 1 dp_{i,j}=dp_{i-1,j-1}+dp_{i-1,j+1} dpi,j=dpi1,j1+dpi1,j+1
它的复杂度为 O ( n t ) O(nt) O(nt),在 n n n 较小的时候较优,但在两者皆较大时时间会炸。

②我们尝试全局去考虑,对于每个坐标系 d p i , j dp_{i,j} dpi,j,它可以由 d p i − 1 , j − 1 + d p i − 1 , j dp_{i-1,j-1}+dp_{i-1,j} dpi1,j1+dpi1,j转化过来,也就是有两种可能,而注意在边界时 d p i , 1 = d p i − 1 , 2 dp_{i,1}=dp_{i-1,2} dpi,1=dpi1,2 d p i , n = d p i − 1 , n − 1 dp_{i,n}=dp_{i-1,n-1} dpi,n=dpi1,n1 ,只有一种可能,所以我们只需要考虑两个特殊值即可。
k i k_i ki表示当前行\列的可能值。
易得 k i + 1 = 2 ∗ k i − d p i , 1 − d p i , n k_{i+1}=2*k_i-dp_{i,1}-dp_{i,n} ki+1=2kidpi,1dpi,n
我们设 g ( x ) g(x) g(x)为从 y y y走到 x x x的方案数。
如果不考虑走出边界的情况,则很容易通过排列组合得到 g ( x ) g(x) g(x)的方案数。
但是若考虑这种情况,则需要减去不符合条件的部分,这里需要运用容斥的方法。
假如点 t t t 关于左边界 0 0 0 的对称点为 L ( t ) = − 2 − t L(t)=-2-t L(t)=2t ,关于右边界 n + 1 n+1 n+1 的对称点为 R ( t ) = 2 ∗ ( n + 1 ) − t R(t)=2*(n+1)-t R(t)=2(n+1)t.
通过容斥我们可以得到 d p i , 1 = g ( 1 ) − g ( l ( 1 ) ) − g ( r ( 1 ) ) + g ( l ( r ( 1 ) ) ) + g ( r ( l ( 1 ) ) ) − . . . dp_{i,1}=g(1)-g(l(1))-g(r(1))+g(l(r(1)))+g(r(l(1)))-... dpi,1=g(1)g(l(1))g(r(1))+g(l(r(1)))+g(r(l(1)))...
由于每次操作都会使操作点 i i i x x x 的距离减少 n n n ,所以该容斥式子的复杂度为 O ( i n ) O(\frac{i}{n}) O(ni)
这样做的复杂度为 O ( T 2 n ) O(\frac{T^2}{n}) O(nT2),对于 n n n 较大时方便实现。

总结:当 n n n 较小时选用动态规划①,当 n n n 较大时选用全局②。

参考代码

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define mk make_pair
#define ,
#define ;
#define (
#define )
#define whlie while
#define viod void
#define mian main
#define read2(x,y) read(x);read(y);
#define FOR(i,n,m) for(int i=n;i<=m;i++)
#define For(i,n,m) for(int i=n;i>=m;i--)
using namespace std ;
void read(int &x)     //快读
{
    int ret=0;
    char c=getchar(),last=' 'whlie(!isdigit(c))
        last=c,c=getchar();
    while(isdigit(c))
        ret=ret*10+c-'0',c=getchar();
    if(last=='-')
        x=-ret;
    else
        x=ret;
}
void read(ll &x)
{
    ll ret=0;
    char c=getchar(),last=' ';
    whlie(!isdigit(c))
        last=c,c=getchar();
    while(isdigit(c))
        ret=ret*10+c-'0',c=getchar();
    if(last=='-')
        x=-ret;
    else
        x=ret;
}
const int N=5e5+5,mod=998244353;
int n,t,m,a,b;
int fac[N],ifac[N],ans1[N],ans2[N];
int dp[2][705];
long long ans=0;
int powmod(int a,int b){int ret=1;while(b){if(b&1)ret=1ll*ret*a%mod;a=1ll*a*a%mod;b>>=1;}return ret;}    //快速幂,求逆元
int C(int a,int b){return 1ll*fac[a]*ifac[b]%mod*ifac[a-b]%mod;};   //求组合数
void solve1(int s,int n,int a,int ans[])
{
    int pos=0;                           //滚动数组
    FOR(j,0,n+1)
        dp[pos][j]=0;
    dp[pos][a]=1;
    ans[0]=1;
    FOR(i,1,s)
    {
        pos=!pos;
        FOR(j,0,n+1)
            dp[pos][j]=0;
        FOR(j,1,n)
        {
            dp[pos][j]=(dp[!pos][j-1]+dp[!pos][j+1])%mod;     //动态转移
            ans[i]=(ans[i]+dp[pos][j])%mod;          //统计
        }
    }
}
int cal(int s,int n,int a,int pos)
{
    int t1=pos,t2=pos,ans=0,q=abs(pos-a);           
    if(q<=s && (s-q)%2==0)             //当距离为奇数时为非法情况,即不做统计
        ans=C(s,(s+q)/2);
    for(int i=1;;i++)           //容斥,一加一减
    {
        if(i&1)
        {
            t1=-t1;
            t2=2*(n+1)-t2;
        }
        else
        {
            t2=-t2;
            t1=2*(n+1)-t1;
        }
        int d1=abs(t1-a),d2=abs(t2-a),det=0;
        if(d1>s && d2>s)                     //若都不符合就返回
            break;
        if(d1<=s && (s-d1)%2==0)          //当距离为奇数时为非法情况,即不做统计
            det=(det+C(s,(s+d1)/2))%mod;
        if(d2<=s && (s-d2)%2==0)          //当距离为奇数时为非法情况,即不做统计
            det=(det+C(s,(s+d2)/2))%mod;
        if(i&1)
            ans=(ans-det+mod)%mod;
        else
            ans=(ans+det)%mod;
    }
    return ans;
}
void solve2(int s,int n,int a,int ans[])
{
    ans[0]=1;
    FOR(i,1,s)
        ans[i]=(2ll*ans[i-1]+mod-cal(i-1,n,a,1)+mod-cal(i-1,n,a,n))%mod;  //减去两种特殊情况
}
void solve(int s,int n,int a,int ans[])
{
    if(1ll*n*n<s)             //对于n较小时选用动态规划
        solve1(s,n,a,ans);
    else                      //当n较大时选用全局考虑
        solve2(s,n,a,ans);
}
int main()
{
    fac[0]=1;
    FOR(i,1,N-1)
        fac[i]=1ll*fac[i-1]*i%mod;
    ifac[N-1]=powmod(fac[N-1],mod-2);
    for(int i=N-1;i>=1;i--)
        ifac[i-1]=1ll*ifac[i]*i%mod;
    read2(t,n)
    read2(m,a)
    read(b);
    solve(t,n,a,ans1);
    solve(t,m,b,ans2);
    FOR(i,0,t)
        ans=(ans+1ll*C(t,i)*ans1[i]%mod*ans2[t-i]%mod)%mod;
    printf("%lld\n",ans);
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值