CF1368H1 Breadboard Capacity
一、题目
\(\tt H2\) 有点毒瘤,不是很想写。
二、解法
首先对原问题建出网络流图,我们把 \(S\) 连所有蓝色接口,\(T\) 连所有红色接口,矩形内的所有点也建出来,向四周连容量为 \(1\) 的无向边,然后对原图跑最大流就是答案。
这里补充一个小知识点,也就是网络流图怎么连无向边,举例:\((u,v)\) 之间一条容量为 \(1\) 的边。
拆分成两条边,\(u\rightarrow v\),\(v\rightarrow u\) 分别连容量为 \(1\) 的互补边,如果我们流 \(u\rightarrow v\) 那么 \(v\rightarrow u\) 会增加一点流量,那么以后流 \(v\rightarrow u\) 的时候相当于交换这两条路径的终点,这条无向边恢复了原来的状态,完成了反悔操作。
肯定不能直接跑最大流,有一个套路是求最小割,再转化一下就是把矩阵的所有点染蓝或者染红(代表最后和起点还是和终点联通),求所有染色方案下的最小端点异色边数。
直接 \(dp\) 根本不行,我们直接构造最优解算了。先考虑整张图都是红色,那么得到的割是蓝色点数,我们考虑把矩形中的一些点改成蓝色使得割更小,首先改点肯定要有点挨着边界,然后考虑要改肯定是改一整行或者一整列,我觉得很显然。
那么得到结论:最优染色方案一定是一整行或者一整列颜色相同。
所以先对行 \(dp\) 一次,再把整个矩阵翻转对列也 \(dp\) 一次即可,时间复杂度 \(O(n)\)
三、总结
要敢于考虑高复杂度做法,一开始把矩形中的每个点都建在网络流里面我是真的没想到,只要你敢想就有优化的可能。
手算最小割是常见套路,通常是考虑每个点属于 \(S\) 或者属于 \(T\) 来考虑染色问题。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,m,q,l[M],r[M],u[M],d[M];char s[M];
void get(int n,int *a)
{
scanf("%s",s+1);
for(int i=1;i<=n;i++)
a[i]=(s[i]=='R');
}
int cal()
{
int f0=0,f1=0,g0,g1;
for(int i=1;i<=m;i++)
f0+=u[i],f1+=(!u[i]);
for(int i=1;i<=n;i++)
{
g0=f0;g1=f1;
f0=min(g0,g1+m)+l[i]+r[i];
f1=min(g0+m,g1)+(!l[i])+(!r[i]);
}
for(int i=1;i<=m;i++)
f0+=d[i],f1+=(!d[i]);
return min(f0,f1);
}
signed main()
{
n=read();m=read();q=read();
get(n,l);get(n,r);get(m,u);get(m,d);
int ans=cal();
swap(n,m);swap(l,u);swap(r,d);
ans=min(ans,cal());
printf("%d\n",ans);
}