Codeforces 1427 G.One Billion Shades of Grey

题目链接 (opens new window)

老年人一看到这么妙的题目就有点激动啊。

题意:给定一个n×n的矩阵,矩阵的边界的每一个位置已经填了一个数,而矩阵中部尚未填数。某些矩阵的中部位置是损坏的不可填数。你要在剩下的矩阵中部位置都填一个数字,对于一种填的方案,其不和谐度是所有四连通的两个都填数的位置所填数字的差的绝对值之和。求最小不和谐度。n200,已经有的边权[1,109]

题解:一道看似简单的题目,却有这么精妙的题解,真好啊。

针对题意,我们构造图G=(V,E)G中每一个节点对应矩阵的一个位置,如果两个位置u,v都可以填数(包括边界)而且相邻,那么E中有边(u,v)(v,u),边权为1

考虑一个恒等式:

|ab|=(k>0[akb>k])+(k>0[bka>k])

那么我们令au代表某一种方案中u所填的数字,就有:

ans=(u,v)E(k>0[aukav>k])=k>0((u,v)E[aukav>k])

可以发现,和式中的最后一项可以用最小割表示。现在我们令B表示所有已经有数字的位置的集合,Ac=VAmc(S,T)表示对于所有SS,TT,ST=,ST=V,图GST割的最小值。也就是源点集至少包含S,汇点集至少包含T的最小割。那么进一步令MC(S,T)表示所有满足SU,TUcmc(U,Uc)=mc(S,T)U的集合,也就是mc(S,T)取到最优值时真实划分情况的解集。

那么上述式子可以表示为:

ans=k>0mc({uV|auk},{uV|au>k})(*)k>0mc({uB|auk},{uB|au>k})

()式是答案的一个下界,现在我们来证明这个下界是可以取到的,也就是答案。

可以取到的条件是,对于k=1,2,,存在W1W2,使得WiMC({uB|aui},{uB|au>i})(这是因为随着k增加,{uV|auk}会扩张而{uV|au>k}会收缩)。

下面的引理则让上一条件从可能成为现实:

对于S~S,T~T,如果WMC(S,T)W~MC(S~,T~),那么WW~MC(S~,T~)

证明:先证明一个式子:

mc(W,Wc)+mc(W~,W~c)mc(WW~,(WW~)c)+mc(WW~,(WW~)c)

左边比右边多了WW~W~W之间的边的贡献,因此成立。

考虑WW~是一种对于(S,T)合法的真实分割情况,而W是最优的情况,因此mc(WW~,(WW~)c)mc(W,Wc)。 同样WW~是一种对于(S~,T~)合法的真实分割情况,而W~是最优的情况,因此mc(WW~,(WW~)c)mc(W~,W~c)

三个不等式结合,必定都是取到等号。因此mc(WW~,(WW~)c)=mc(W~,W~c),引理成立。

也就是说,只要我们找到每一个MC({uB|aui},{uB|au>i})中的一个解,就一定可以构造一种合法的W1W2。因此我们现在只需要关注如何快速计算这些最小割。

由于|B|=O(n),因此集合划分情况最多变动O(n)次,只需要算O(n)次最小割。但图的规模是O(n2)的,不能每次重来一遍。

现在我们来考虑实现最大流时的真实情况。每一次将一个点v从汇点集移动到源点集,也就是将(v,t)这条容量为的边删除,加上(s,v)这条容量为的边。考虑保留上一次的残量网络,由于v的入度不超过3,只要反向反复深度搜索每一条经过v的流退流后即可删除边(v,t),且最多退流O(1)次。添加边(s,v)后,由于v出度不超过3,在残量网络上暴力增广,增广次数也是O(1)的。每一次增广或退流的复杂度为O(n2),总复杂度为O(n3)

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using std::min;
using std::sort;
using std::vector;
const int inf=0x3f3f3f3f;
namespace mf
{
	const int N=1e5+5;
	int n,s,t,ans;
	struct edge
	{
		int v,w,inv;
		bool ex;
	};
	vector<edge> e[N];
	bool bk[N];
	inline edge &inv(edge ed)
	{
		return e[ed.v][ed.inv];
	}
	inline void add_edge(int u,int v,int w)
	{
		e[u].push_back(edge{v,w,(int)e[v].size(),1});
		e[v].push_back(edge{u,0,(int)e[u].size()-1,1});
		return;
	}
	int dfs(int x,int f)
	{
		if(x==t)
			return f;
		int tot=0,d;
		bk[x]=1;
		for(edge &ed:e[x])
			if(ed.ex&&ed.w&&!bk[ed.v]&&(d=dfs(ed.v,min(f-tot,ed.w))))
			{
				ed.w-=d;inv(ed).w+=d;
				if((tot+=d)==f)
					break;
			}
		return tot;
	}
	int idfs(int x,int f,int y)
	{
		if(x==s)
			return f;
		int tot=0,d;
		bk[x]=1;
		for(edge &ed:e[x])
			if(ed.ex&&ed.w&&!bk[ed.v]&&(x!=t||ed.v==y)&&(d=idfs(ed.v,min(f-tot,ed.w),y)))
			{
				ed.w-=d;inv(ed).w+=d;
				if((tot+=d)==f)
					break;
			}
		return tot;
	}
	inline void shrink(int x)
	{
		int f;
		while(memset(bk+1,0,sizeof(bool)*n),f=idfs(t,inf,x))
			ans-=f;
		for(edge &ed:e[x])
			if(ed.v==t)
				ed.ex=0,inv(ed).ex=0;
		add_edge(s,x,inf);
		while(memset(bk+1,0,sizeof(bool)*n),f=dfs(s,inf))
			ans+=f;
		return;
	}
}
const int N=205;
int n,cur;
long long ans;
int a[N][N],b[N*N];
vector<int> V;
inline int id(int x,int y)
{
	return (x-1)*n+y;
}
inline bool cmp(int x,int y)
{
	return b[x]<b[y];
}
signed main()
{
	register int i,j;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
			scanf("%d",&a[i][j]),b[id(i,j)]=a[i][j];
	mf::n=n*n+2;mf::s=n*n+1;mf::t=n*n+2;
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
			if(a[i][j]>0)
				V.push_back(id(i,j));
	sort(V.begin(),V.end(),cmp);
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
			if(a[i][j]>0)
				mf::add_edge(id(i,j),mf::t,inf);
	for(i=1;i<=n;i++)
		for(j=1;j<n;j++)
			if((~a[i][j])&&(~a[i][j+1]))
			{
				mf::add_edge(id(i,j),id(i,j+1),1);
				mf::add_edge(id(i,j+1),id(i,j),1);
			}
	for(i=1;i<n;i++)
		for(j=1;j<=n;j++)
			if((~a[i][j])&&(~a[i+1][j]))
			{
				mf::add_edge(id(i,j),id(i+1,j),1);
				mf::add_edge(id(i+1,j),id(i,j),1);
			}
	for(int x:V)
	{
		ans+=(long long)(b[x]-cur)*mf::ans;
		mf::shrink(x);cur=b[x];
	}
	printf("%lld\n",ans);
	return 0;
}