CF1503E 2-Coloring
CF1503E 2-Coloring
题目大意
略过。
做法解析
不会组合,使用了 DP
,但其实本质相同。
我们假设所有的格子都是蓝色的,然后考虑将一些格子换成黄色的。
我们考虑从每一行的两头开始将格子换成黄色,只要不把整一行都换成黄色的我们就可以保证每一行恰好有一段蓝色的格子。
为了保证每一列恰好有一段黄色的格子,我们从一头开始替换的黄色格子必须是单峰的,可以有多个最大值,并不严格。
并且两头的峰必须是并集为整一行的,这样才能保证恰好都有一段连续的黄色格子。
见下图(我认为代码块更加清晰):
0
表示蓝色,1
表示黄色。
1 0 0 0 0 0 0 0 0 1
1 1 1 1 0 0 0 0 0 1
1 1 1 1 1 0 0 0 1 1
1 1 0 0 0 0 1 1 1 1
0 0 0 0 0 1 1 1 1 1
并且如果两个峰有重叠部分,那么他们必须是连续在一起的。
例如,这样是不对的,不满足恰有一段连续的黄色。
1 0 0 0 0 0 0 0 0 1
1 1 1 1 0 0 0 0 0 1
1 1 1 1 1 1 1 0 1 1
1 1 0 0 0 0 1 1 1 1
0 0 0 0 0 1 1 1 1 1
但是这样是对的:
1 0 0 0 0 0 0 0 0 1
1 1 1 1 0 0 0 0 0 1
1 1 1 1 1 1 1 0 1 1
1 1 0 0 0 1 1 1 1 1
0 0 0 0 0 1 1 1 1 1
接着我们发现一件事情,对于两种颜色其实他们是等价的,只是把图形旋转九十度来看而已。
我们考虑证明任意一个合法的图形必有一种颜色满足其的两峰没有重叠部分。
证明是简单的,如果其中一种颜色的两峰有重叠部分,那么这两峰必然是连续在一起的,那么对于另一种颜色的两峰恰好就是没有重叠部分的了。
所以我们可以考虑统计一种颜色的两峰恰好没有重叠部分的方案再减去两种颜色同时两峰恰好没有重叠部分的方案,这样就求出了答案。
考虑如何统计一种颜色的两峰恰好没有重叠部分的方案。
枚举两峰位置 \(i\),\(j\),其中一峰高度 \(x\) 那么另一峰高度为 \(m-x\),注意错开 \(i\),\(j\) 的位置,不然就并完一整行了。
由于单峰,我们可以拆成两个钦定不上升的序列的方案数的乘积。
这个如何求呢,可以组合,但是我使用了 DP
。
设 \(f(i,j)\) 表示最大值为 \(i\) 长度为 \(j\) 的不上升子序列个数。
那么有状态转移方程。
比较明显的前缀和优化,可以 \(O(n^2)\) 预处理。
现在还有一个问题,由于单峰不是严格的,所以我们就算错开了 \(i\),\(j\) 仍然无法避免他们并完一整行。
我们考虑在前面的峰是最后一个相同的最大值,在后面的峰是第一个相同的最大值,这样就可以巧妙的避免上面的问题了。
总的来说答案等于:
后面的部分可以用后缀和优化。
时间复杂度 \(O(n^2)\)。
对于另外一种颜色,记得交换 \(n\),\(m\)。
接下来减去两种颜色同时恰好没有重叠部分的方案。
这样看起来是一个风车的样子,如下图,图中 I
实际为 \(1\):
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 1 1 I 0 0 1
1 1 0 0 1 1 1
1 0 0 0 0 1 1
1 0 0 0 0 0 0
我们枚举左上角 \((i,j)\)(I
位置),这样的方案数实际为。
时间复杂度 \(O(n^2)\)。
总时间复杂度 \(O(n^2)\)。
后记
赛场想了三个小时的想法,最后因为没有看到最后的后缀和优化遗憾离场。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
const int maxn=2e3;
int n,m;
int f[maxn+5][maxn+5];
int g[maxn+5][maxn+5];
int h[maxn+5][maxn+5];
int ans;
inline void Init(){
for(int i=0;i<=maxn;i++){
f[i][0]=1;
if(i) g[i][0]=(g[i-1][0]+f[i][0])%mod;
else g[i][0]=f[i][0];
}
for(int j=1;j<=maxn;j++){
for(int i=0;i<=maxn;i++){
f[i][j]=g[i][j-1];
if(i) g[i][j]=(g[i-1][j]+f[i][j])%mod;
else g[i][j]=f[i][j];
}
}
}
inline void Calc(){
memset(h,0,sizeof(h));
for(int i=n-1;i>=1;i--){
for(int k=1;k<m;k++){
h[i][k]=(h[i+1][k]
+f[m-k-1][i]*f[m-k][n-i-1]%mod)%mod;
}
}
}
inline void Solve(){
Calc();
for(int i=1;i<=n;i++){
for(int k=1;k<m;k++){
ans=(ans+2*f[k][i-1]%mod
*f[k-1][n-i]%mod*h[i][k]%mod)%mod;
}
}
}
inline void Work(){
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
ans=(ans-2*f[j-1][n-i]%mod*f[m-j-1][i]%mod
*f[i-1][j]%mod*f[n-i-1][m-j]%mod)%mod;
}
}
}
signed main(){
// freopen("magic.in","r",stdin);
// freopen("magic.out","w",stdout);
Init();
scanf("%lld%lld",&n,&m);
Solve();
swap(n,m);
Solve();
Work();
printf("%lld",(ans%mod+mod)%mod);
return 0;
}