AGC046B-Extension题解
题意:有一个
的矩阵,所有格子全为白色。每次可以选择往右添加一列或网上添加一行白格子,并选择添加的其中一个格子染成黑色,问变成 的矩阵时图案的方案数。
做法一
By betrue12
B - 扩展
首先考虑以下 DP
看起来,这个 DP 中的 "在上面加一排,把其中一排涂成黑色 "的转移和 "在右边加一列,把其中一列涂成黑色 "的转移已经足够了,但重要的是最后的板数,所以必须注意,即使中间的操作列不一样,也不要把同一个东西重复成板。
尽管操作列不同,但有结果相同的操作,特别是
因此,在一个
- 右上角不是黑色的
- 最上面一行和最右边一列都有一个黑色方块
- 一个或多个行和列已经从初始状态添加了
满足 的所有条件,可以通过两种不同的方式从 板中得到。
更一般地说,如果有
从以下事实可以看出,只有这样的重复模式,对于任何其他棋盘来说,最多只有一个可以考虑反向操作(去除最上面的一行或最右边的一列,正好有一个黑色的方块)的操作,而不会把下一个操作也卡住。
因此,DP 的规则是,只有一个具体的这样的操作序列可以被采纳为一个过渡。 例如,假设我们只采用 "将所有行添加到顶部,然后将所有列添加到右侧"。 在这种情况下,当过渡到右侧添加一列时
- 除了该列的顶部边缘(右上角)外,其他地方都要涂抹。
- 过渡前最上面一行的一个黑色方块
- 自初始状态以来,已经添加了一个或多个行
如果满足上述所有条件,那么这个过渡应该被取消,因为它是一个被计入不同操作行的棋盘。
为了能够确定这一点,应将DP表扩展到
(实际上,A或B都可以)。 由于这里使用的决定是 "是否正好有一个黑方块",因此将 "0"、"1 "和 "2以上 "区分为
通过将过渡划分为总共四个过渡--"在顶部添加一行,并将其右侧/非右侧边缘涂成黑色 "和 "在右侧添加一列,并将其顶部/非顶部边缘涂成黑色"--可以计算出过渡的
答案是
int main(){
int A, B, C, D;
cin >> A >> B >> C >> D;
static int64_t dp[3002][3002][3][3];
dp[A][B][0][0] = 1;
for(int i=A; i<=C; i++) for(int j=B; j<=D; j++) for(int a=0; a<3; a++) for(int b=0; b<3; b++){
// 上、隅以外
{
add(dp[i+1][j][1][b], dp[i][j][a][b] * (j-1));
}
// 上、隅
{
add(dp[i+1][j][1][min(2, 1+b)], dp[i][j][a][b]);
}
// 右、隅以外
{
if(a != 1 || i==A) add(dp[i][j+1][a][1], dp[i][j][a][b] * (i-1));
}
// 右、隅
{
add(dp[i][j+1][min(2, 1+a)][1], dp[i][j][a][b]);
}
}
int64_t ans = 0;
for(int a=0; a<3; a++) for(int b=0; b<3; b++) add(ans, dp[C][D][a][b]);
cout << ans << endl;
return 0;
}
做法二
同样考虑 DP:
这个题的难点主要在如何去重。首先发现一个性质:如果算出来的
每一步转移的去重为,在
思考一下可以发现,如果这
反之,如果右上角的格子为白色,则一定既能从行转移也能从列转移吗?并不是。如果最右边的列有超过一个黑格子,最后一步一定不能添加右边的列。最上面的行同理。
所以,既能从行转移也能从列转移的充要条件为:右上角为白格子,最上面一行和最右边一列都只有一个黑格子。这样的方案数有多少呢?显然为
于是最终的转移方程非常简单:
By cxm1024
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int f[3010][3010];
signed main() {
int a,b,c,d;
cin>>a>>b>>c>>d;
f[a][b]=1;
for(int i=a;i<=c;i++)
for(int j=b;j<=d;j++) {
if(i==a&&j==b) continue;
f[i][j]=(1ll*f[i-1][j]*j%mod+1ll*f[i][j-1]*i%mod-1ll*f[i-1][j-1]*(i-1)%mod*(j-1)%mod+mod)%mod;
}
cout<<f[c][d]<<endl;
return 0;
}
做法三
设
By ecnerwala
int main() {
using namespace std;
ios_base::sync_with_stdio(false), cin.tie(nullptr);
using num = modnum<998244353>;
int A, B, C, D; cin >> A >> B >> C >> D;
assert(A <= C && B <= D);
tensor<array<num, 2>, 2> dp({C+1, D+1});
dp[{A,B}][1] ++;
for (int i = A; i <= C; i++) {
for (int j = B; j <= D; j++) {
if (i < C) {
dp[{i+1, j}][0] += dp[{i,j}][0] * j;
dp[{i+1, j}][0] += dp[{i,j}][1] * j;
}
if (j < D) {
dp[{i, j+1}][1] += dp[{i,j}][0];
dp[{i, j+1}][1] += dp[{i,j}][1] * i;
}
}
}
cout << dp[{C,D}][0] + dp[{C,D}][1] << '\n';
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步