CF1592F Alice and Recoloring
CF1592F1 Alice and Recoloring 1
CF1592F2 Alice and Recoloring 2
Part1
很容易发现,其实二和三操作是没有用的.
对于二操作,可以用一操作进行类似于求二维前缀和的方式实现,需要进行一操作至多 \(2\) 次,花费为 \(1+1=2\), 和直接进行一次二操作的代价是一样的。对于三操作也是一样的。
而对于四操作,需要进行最多 \(4\) 次一操作才能实现,说明四操作是有用的。
考虑把问题逆过来,即给定矩阵,要最小花费把其全变成白格子,正确性显然,操作是可逆的。
Part2
每次翻转一个矩形不好考虑,考虑问题的转化。
设一个格子的权值为 \(w_{i,j}\), 我们把白格子的权值设为 \(0\), 黑格子的权值设为 \(1\), 设 $a_{i,j}=(w_{i,j}+w_{i+1,j}+w_{i,j+1}+w_{i+1,j+1}) \mod 2 $
那把所有格子变为白格子的充要条件为 每个 \(a_{i,j}\) 都变为零。
必要性显然。
考虑证明充分性:从右下角开始,\(a_{n,m}=0\) 当且仅当 \(w_{n,m}=0\) ,原因显然。那么 \(w{n-1,m}\) \(w_{n,m-1}\) 也一定为零,类推下去,整个矩阵就都成 \(0\) 了。
对于一操作 \((x,y)\), 相当于翻转 \(a_{x,y}\)
对于四操作 \((x,y)\), 相当于翻转 \(a_{n,m}\) \(a_{x-1,m}\) \(a_{n,y-1}\) \(a_{x-1,y-1}\)。 可以分情况讨论。
Part3
对于问题一:
我们又可以证明:我们进行四操作有两个性质:
- 只有\(a_{n,m}=a_{x-1,m}=a_{n,y-1}=a_{x-1,y-1}=1\),才会进行操作四。
如果只有三个为 \(1\), 等价于进行三次一操作,少于 \(3\) 就不优了。
- 操作四最多进行一次。
根据性质一,进行一次操作后,\(a_{n,m}\) 一定为 \(0\), 那么再进行四操作最多把三个变为 \(0\), 把 \(a_{n,m}\) 变为 \(1\), 还需要进行一次一操作把 \(a_{n,m}\) 变为 \(0\), 代价为 \(3+1=4\) 不优了。所以操作四最多进行一次。
那直接判断可否进行四操作,然后统计一的个数就可以了。
复杂度 \(\Theta(nm)\)
Code
#include <iostream>
#include <cstdio>
const int N=510;
using namespace std;
inline void write(int x) {
cout<<x;
}
int n, m;
int a[N][N], b[N][N];
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
char ch; cin>>ch;
if(ch=='B') a[i][j]=1;
else a[i][j]=0;
}
}
int ans=0;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
b[i][j]=(a[i][j]^a[i+1][j]^a[i][j+1]^a[i+1][j+1]);
ans+=b[i][j];
}
}
if(b[n][m]) {
bool flag=0;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
if(b[i-1][j-1] && b[i-1][m] && b[n][j-1]) {
ans--, flag=1;
break;
}
}
if(flag) break;
}
}
write(ans);
return 0;
}
Part4
对于问题二:
我们又可以证明:我们进行四操作也有两个性质:
- 不会同时使用 \((x,y1)\) 和 \((x,y2)\), 同理不会同时使用 \((x1,y)\) 和 \((x2,y)\)。
以第一种为例,翻转了 \((x-1,m)\) 和 \((n,m)\) 两次,相当于没有翻转,实际翻转的只有 \(2+2=4\) 个,代价为 \(4\), 不比操作一优。
- 只有 \((x, y)\),\((n, y)\) 和 \((x, m)\) 都为 \(1\) ,才会使用 \((x, y)\)。
如果不都为一,就会发生一次错误的翻转,还要进行一次一操作,代价为 \(2+1=3\), 不比操作一优。
那么可以建出一张二分图,如果满足性质二,直接连边,跑二分图最大匹配即可。
最后需要特判 \(a_{n,m}\).
复杂度 \(\Theta(n^2m)\)
Code
#include <iostream>
#include <cstdio>
const int N=510;
using namespace std;
inline void write(int x) {
cout<<x;
}
int n, m, cnt_edge;
int a[N][N];
struct edge{
int next, to;
}e[N*N*2];
int head[N<<1];
int vis[N<<1], match[N<<1];
void add_edge(int u,int v) {
e[++cnt_edge].to=v;
e[cnt_edge].next=head[u];
head[u]=cnt_edge;
}
bool dfs(int now,int tag) {
if(vis[now]==tag) return 0;
vis[now]=tag;
for(int i=head[now];i;i=e[i].next) {
int v=e[i].to;
if(!match[v] || dfs(match[v], tag)) {
match[v]=now;
return 1;
}
}
return 0;
}
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
char ch; cin>>ch;
if(ch=='B') a[i][j]=1;
else a[i][j]=0;
}
}
int ans=0;
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
a[i][j]^=(a[i+1][j]^a[i][j+1]^a[i+1][j+1]);
ans+=a[i][j];
}
}
for(int i=1;i<n;i++) {
for(int j=1;j<m;j++) {
if(a[i][j] && a[n][j] && a[i][m]) {
add_edge(i, j+n);
}
}
}
int num=0;
for(int i=1;i<=n;i++) {
if(dfs(i, i)) num++;
}
ans-=a[n][m];
write(ans-num+((a[n][m]^num)&1));
return 0;
}