CF1592F Alice and Recoloring
题目
有一个 \(n\times m\) 的矩阵,位置 \((i,j)\) 上的颜色是白色或者黑色,最小化变成全白的代价,操作如下:
- 反转一个包含 \((1,1)\) 的子矩阵,花费为 \(1\)
- 反转一个包含 \((n,1)\) 的子矩阵,花费为 \(2/3\)
- 反转一个包含 \((1,m)\) 的子矩阵,花费为 \(4\)
- 反转一个包含 \((n,m)\) 的子矩阵,花费为 \(3/2\)
\(n,m\leq 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\n",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\n",ans-mt);
}