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;
}
posted @ 2023-10-29 19:04  Trmp  阅读(21)  评论(3编辑  收藏  举报
-->