题目
有一个 (n imes m) 的矩阵,位置 ((i,j)) 上的颜色是白色或者黑色,最小化变成全白的代价,操作如下:
- 反转一个包含 ((1,1)) 的子矩阵,花费为 (1)
- 反转一个包含 ((n,1)) 的子矩阵,花费为 (2/3)
- 反转一个包含 ((1,m)) 的子矩阵,花费为 (4)
- 反转一个包含 ((n,m)) 的子矩阵,花费为 (3/2)
(n,mleq 500)
Easy version
容易发现第 (2,3) 种操作一定没用,因为它们都可以被一操作替换。
因为反转操作是范围修改,我们尝试使用类似差分的技巧让它修改某个单点值,把白色当成 (0),把黑色当成 (1),设 (b_{i,j}=a_{i,j}oplus a_{i,j+1}oplus a_{i+1,j}oplus a_{i+1,j+1}),最后的状态是全为 (0)
那么操作 (1) 相当于反转 (b_{i,j}),操作 (4) 相当于反转 (b_{i-1,m},b_{n,j-1},b_{i-1,j-1},b_{n,m})
根据贪心当且仅当四个位置都有值的时候才会使用 (4) 操作,否则使用 (1) 操作是最优的,那么我们暴力判断有没有这样的位置 ((i,j)),如果没有答案就是 (b_{x,y}=1) 的个数,否则答案需要减去 (1)
Hard version
因为 (4) 操作的代价变小了,所以它的出场机会会变多,不妨先不考虑 (b_{n,m})(影响太小)
那么如果 (b_{i-1,m},b_{n,j-1},b_{i-1,j-1}) 三个位置都有值,就可以使用 (4) 操作来减少 (1) 的代价,否则都不能减少代价。很容易看出这是一个行和列的匹配问题,我们找到最大匹配就是最大代价减少量。
这时候再来考虑 (b_{n,m}),如果匹配个数是奇数,若 (b_{n,m}=0) 则需要多花费 (1) 的代价(但是暴力匹配还是最优的),若 (b_{n,m}=0) 则可以减少 (1) 的代价,如果匹配个数为偶数则无影响。
总结
本题最关键的技巧是广义差分,一维数组上我们通过差分可以把区间修改变成单点修改。在矩阵上同样可以使用差分技巧,那么可以把矩阵操作变成单点操作,用四个相邻位置来定义差分值即可。
//I might be staring at my last chance
#include <cstdio>
const int M = 505;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans,a[M][M],b[M][M];char s[M][M];
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]+1);
for(int j=1;j<=m;j++)
a[i][j]=s[i][j]=='B';
}
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];
if(b[i][j]) ans++;
}
int sub=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(b[n][m] && b[i-1][m]
&& b[n][j-1] && b[i-1][j-1])
sub=1;
printf("%d
",ans-sub);
}
//I might be staring at my last chance
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 1005;
const int inf = 0x3f3f3f3f;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans,tot,f[M],a[M][M],b[M][M];
int S,T,mt,dis[M],cur[M];char s[M][M];
struct edge{int v,c,next;} e[M*M];
void add(int u,int v,int c)
{
e[++tot]=edge{v,c,f[u]},f[u]=tot;
e[++tot]=edge{u,0,f[v]},f[v]=tot;
}
int bfs()
{
for(int i=0;i<=T;i++) dis[i]=0;
queue<int> q;
q.push(S);dis[S]=1;
while(!q.empty())
{
int u=q.front();q.pop();
if(u==T) return 1;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(!dis[v] && e[i].c>0)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int ept)
{
if(u==T) return ept;
int flow=0,tmp=0;
for(int &i=cur[u];i;i=e[i].next)
{
int v=e[i].v;
if(dis[v]==dis[u]+1 && e[i].c>0)
{
tmp=dfs(v,min(ept,e[i].c));
if(!tmp) continue;
ept-=tmp;
e[i].c-=tmp;
e[i^1].c+=tmp;
flow+=tmp;
if(!ept) break;
}
}
return flow;
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]+1);
for(int j=1;j<=m;j++)
a[i][j]=s[i][j]=='B';
}
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];
if(b[i][j]) ans++;
}
S=0;T=n+m+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(b[i-1][m] && b[n][j-1] && b[i-1][j-1])
add(i,n+j,1);
for(int i=1;i<=n;i++)
add(S,i,1);
for(int i=1;i<=m;i++)
add(i+n,T,1);
while(bfs())
{
for(int i=0;i<=T;i++)
cur[i]=f[i];
mt+=dfs(S,inf);
}
if(mt%2)
{
if(a[n][m]==0) ans++;
else ans--;
}
printf("%d
",ans-mt);
}