F. Alice and Recoloring 1&2

F1. Alice and Recoloring 1

题意:给定一个 n * m 的 0 1 矩阵,你需要使用以下四种操作使得个矩阵全为 0 ,求出最小的代价。

  • 代价为 1 的第一种操作:反转矩阵的某个左上子矩阵。
  • 代价为 2 的第二种操作:反转矩阵的某个左下子矩阵。
  • 代价为 4 的第三种操作:反转矩阵的某个右上子矩阵。
  • 代价为 3 的第四种操作:反转矩阵的某个右下子矩阵。

分析:

  • 首先需要发现第二种和第三种操作是可以被第一种操作替代的,且代价不会更高。
  • 将原先的矩阵做差分。原先对关于(x,y)的子矩阵的操作,对于差分后的矩阵,第一种操作等价于反转(x,y)一个单元,第四种操作相当于反转(x-1,y-1),(x-1,m),(n,y-1),(n,m)这四个单元。
  • 考虑第四种操作什么时候是有意义的,显现需要这个操作使得四个单元都由 0 变成 1 。又由于它们都是关于(n,m)的,所以该操作最多执行一次。
#include<bits/stdc++.h>
#define ll long long
#define ls u<<1
#define rs u<<1|1
#define mm(x) memset(x,0,sizeof(x))
#define debug(x) cout << #x << ":" << x << '
'
using namespace std;
int read()
{
    int a=0;int f=0;char p=getchar();
    while(!isdigit(p)){f|=p=='-';p=getchar();}
    while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=getchar();}
    return f?-a:a;
}
const int INF=998244353;
int T;
int n,m;
int ans;
char s[550][550];
bool val[550][550];
int main()
{
    n=read();    m=read();
    for(int i=1;i<=n;++i)    scanf("%s",s[i]+1);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            val[i][j]=s[i][j]=='B';
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            val[i][j]^=val[i+1][j]^val[i][j+1]^val[i+1][j+1];
    ans=val[n][m];
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
            if(i==n&&j==m)    continue;
            if(val[i][j])
            {
                ans++;
                if(i==n)    continue;
                if(j==m)    continue;
                if(val[i][m]&&val[n][j]&&val[n][m])
                {
                    ans--;
                    val[n][m]=false;
                }
            }
        }
    printf("%d",ans);
    return 0;
}
View Code

F2. Alice and Recoloring 2

题意:给定一个 n * m 的 0 1 矩阵,你需要使用以下四种操作使得个矩阵全为 0 ,求出最小的代价。

  • 代价为 1 的第一种操作:反转矩阵的某个左上子矩阵。
  • 代价为 2 的第二种操作:反转矩阵的某个左下子矩阵。
  • 代价为 4 的第三种操作:反转矩阵的某个右上子矩阵。
  • 代价为 2 的第四种操作:反转矩阵的某个右下子矩阵。

分析:相比于前一题,第四种操作的代价变小了。造成的结果是,第四种操作能执行多次。

  • 依旧是首先将原先的矩阵做差分。
  • 考虑第四种操作作用的点。假设对于同一行或者同一列的两个单元执行第四种操作,那么我们相当于花费了 4 的代价反转了 4 个单元。并不比第一种操作更加节约,因此我们避免这种操作。
  • 继续考虑第四种操作,如果个操作影响的四个单元中为 1 的个数少于或者等于三个,考虑到要把至少一个单元反转回来,显然也不会更节约。
  • 由于(n,m)是所有第四种操作都会影响的单元,我们暂时先不考虑它。我们考虑是否要对一个单元(x,y)执行第四种操作时,首先保证(x,y),(x,m),(n,y)都为 1 ,将它加入备选队列。
  • 接下来我们需要的是在备选队列中选出尽可能多的点,满足没有任意两个点的 X 或者 Y 相等(这是一个经典的关于网格的二分图匹配问题)。
#include<bits/stdc++.h>
#define ll long long
#define ls u<<1
#define rs u<<1|1
#define mm(x) memset(x,0,sizeof(x))
#define debug(x) cout << #x << ":" << x << '
'
using namespace std;
int read()
{
    int a=0;int f=0;char p=getchar();
    while(!isdigit(p)){f|=p=='-';p=getchar();}
    while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=getchar();}
    return f?-a:a;
}
const int INF=998244353;
int T;
int n,m;
int ans,tot;
char s[550][550];
bool val[550][550];
bool link[550][550];
int mat[550];
int vis[550];
bool dfs(int u,int t)
{
    for(int i=1;i<=m;++i)
    {
        if(link[u][i]&&vis[i]!=t)
        {
            vis[i]=t;
            if(!mat[i]||dfs(mat[i],t))
            {
                mat[i]=u;
                return true;
            }
        }
    }
    return false;
}
int main()
{
    n=read();    m=read();
    for(int i=1;i<=n;++i)    scanf("%s",s[i]+1);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            val[i][j]=s[i][j]=='B';
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
        {
            val[i][j]^=val[i+1][j]^val[i][j+1]^val[i+1][j+1];
            ans+=val[i][j]; 
        }
    for(int i=1;i<n;++i)
        for(int j=1;j<m;++j)
            if(val[i][j]&&val[i][m]&&val[n][j])
                link[i][j]=true;
    for(int i=1;i<=n;++i)
        if(dfs(i,i))    ++tot;
    ans-=val[n][m];
    ans-=tot;
    if((tot^val[n][m])&1)    ans++;
    printf("%d",ans);
    return 0;
}
View Code