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 
#include 
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);
}