【基础 DP,但注意写法上的细节】

本文介绍了一种使用动态规划解决AcWing周赛中摆放棋子问题的方法。通过定义状态f(i,j,k,u)表示选取i个黑子、j个白子,末尾有连续k个棋子,u表示末位颜色的状态,实现状态转移,求解方案数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AcWing 80 场周赛 4727.摆放棋子

算法:动态规划

  1. f ( i , j , k , u ) f(i, j, k, u) f(i,j,k,u) —— 表示选 i i i 个黑子, j j j 个白子,末尾有连续 k k k 个棋子, u = 0 u = 0 u=0 表示末位是黑子, u = 1 u = 1 u=1 表示末位是白子。

  2. 状态转移方程:若末位只有一个连续棋子需要特判

f ( i , j , k , 0 ) = f ( i − 1 , j , k − 1 , 0 ) f(i, j, k, 0) = f(i - 1, j, k - 1, 0) f(i,j,k,0)=f(i1,j,k1,0)

f ( i , j , k , 1 ) = f ( i , j − 1 , k − 1 , 1 ) f(i, j, k, 1) = f(i, j - 1, k - 1, 1) f(i,j,k,1)=f(i,j1,k1,1)

f ( i , j , 1 , 0 ) + = f ( i − 1 , j , k , 1 ) f(i, j, 1, 0) += f(i - 1, j, k, 1) f(i,j,1,0)+=f(i1,j,k,1)

f ( i , j , 1 , 1 ) + = f ( i , j − 1 , k , 0 ) f(i, j, 1, 1) += f(i, j - 1, k, 0) f(i,j,1,1)+=f(i,j1,k,0)

时间复杂度 O ( n 1 n 2 × ( k 1 + k 2 ) ) O(n_1 n_2 \times (k_1 + k_2)) O(n1n2×(k1+k2))
#include <iostream>
#include <algorithm>
#include <cstring>

#define endl '\n'

#define fup(i, a, b) for (int i = a; i <= b; i ++ )
#define fdn(i, a, b) for (int i = a; i >= b; i -- )

using namespace std;

typedef long long LL;

const int N = 110, M = 15, MOD = 1e8;

int n, m, x, y;
int f[N][N][M][2];

void add(int &a, int b)
{
    a = (a + b) % MOD;
}

int main()
{
    cin >> n >> m >> x >> y;
    
    f[1][0][1][0] = f[0][1][1][1] = 1;
    fup(i, 0, n) fup(j, 0, m)
    {
        if (i) 
        {
            fup(k, 2, x) f[i][j][k][0] = f[i - 1][j][k - 1][0];
            fup(k, 1, y) add(f[i][j][1][0], f[i - 1][j][k][1]);
        }
        if (j)
        {
            fup(k, 2, y) f[i][j][k][1] = f[i][j - 1][k - 1][1];
            fup(k, 1, x) add(f[i][j][1][1], f[i][j - 1][k][0]);
        }
    }
    
    int res = 0;
    fup(i, 1, x) add(res, f[n][m][i][0]);
    fup(i, 1, y) add(res, f[n][m][i][1]);
    
    cout << res << endl;
    
    return 0;
}

简洁写法

  1. f ( i , j , k ) f(i, j, k) f(i,j,k) 表示摆好前 i i i 个棋子,其中有 j j j 个白棋, k = 0 k = 0 k=0 表示最后一位为黑棋, k = 1 k = 1 k=1 表示最后一位为白棋。

  2. 状态转移方程:若最后一位是白棋,则枚举砍掉最后多少位后剩下的序列的最后一位是黑棋,这里枚举砍掉 k k k 位的范围是 1 ⩽ k ⩽ m i n ( j , x ) 1 \leqslant k \leqslant min(j, x) 1kmin(j,x),表示最后砍掉的部分不能超过当前枚举的连续段的上限以及题目所给的连续长度的上限,下面同理;若最后一位是黑棋,则枚举砍掉最后多少位后剩下的序列的最后一位是白棋,这里枚举砍掉 k k k 位的范围是 1 ⩽ k ⩽ m i n ( i − j , y ) 1 \leqslant k \leqslant min(i - j, y) 1kmin(ij,y)

f ( i , j , 1 ) + = f ( i − k , j − k , 0 ) f(i, j, 1) += f(i - k, j - k, 0) f(i,j,1)+=f(ik,jk,0)

f ( i , j , 0 ) + = f ( i − k , j , 1 ) f(i, j, 0) += f(i - k, j, 1) f(i,j,0)+=f(ik,j,1)

时间复杂度 O ( ( n 1 + n 2 ) × k 2 ) O((n_1 + n_2) \times k_2) O((n1+n2)×k2)
#include <iostream>
#include <algorithm>
#include <cstring>

#define endl '\n'

#define fup(i, a, b) for (int i = a; i <= b; i ++ )
#define fdn(i, a, b) for (int i = a; i >= b; i -- )

using namespace std;

const int N = 110, M = N << 1, S = 15, MOD = 1e8;

int n, m, x, y;
int f[M][N][S];

int add(int &a, int b) 
{
    return a = (a + b) % MOD;
}

int main()
{
    cin >> n >> m >> x >> y;
    n += m;
    
    f[0][0][0] = f[0][0][1] = 1;
    fup(i, 1, n) fup(j, 0, min(i, m))
    {
        fup(k, 1, min(j, y)) add(f[i][j][1], f[i - k][j - k][0]);
        fup(k, 1, min(i - j, x)) add(f[i][j][0], f[i - k][j][1]);
    }
    
    cout << add(f[n][m][0], f[n][m][1]) << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值