• Codeforces 1427 G.One Billion Shades of Grey


    题目链接

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

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

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

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

    考虑一个恒等式:

    [|a-b|=igg(sum_{k>0}[aleq kland b>k]igg)+igg(sum_{k>0}[bleq kland a>k]igg) ]

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

    [ans=sum_{(u,v)in E}igg(sum_{k>0}[a_uleq kland a_v>k]igg) ]

    [=sum_{k>0}igg(sum_{(u,v)in E}[a_uleq kland a_v>k]igg) ]

    可以发现,和式中的最后一项可以用最小割表示。现在我们令(B)表示所有已经有数字的位置的集合,(A^c=Vsetminus A)(mc(S,T))表示对于所有(Ssubseteq S',Tsubseteq T',S'cap T'=emptyset,S'cup T'=V),图(G)(S'-T')割的最小值。也就是源点集至少包含(S),汇点集至少包含(T)的最小割。那么进一步令(MC(S,T))表示所有满足(Ssubseteq U,Tsubseteq U^c)(mc(U,U^c)=mc(S,T))(U)的集合,也就是(mc(S,T))取到最优值时真实划分情况的解集。

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

    [ans=sum_{k>0}mc({uin V|a_uleq k},{uin V|a_u>k}) ]

    [geq sum_{k>0}mc({uin B|a_uleq k},{uin B|a_u>k}) ag{*} ]

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

    可以取到的条件是,对于(k=1,2,dots),存在(W_1subseteq W_2subseteq dots),使得(W_iin MC({uin B|a_uleq i},{uin B|a_u>i}))(这是因为随着(k)增加,({uin V|a_uleq k})会扩张而({uin V|a_u>k})会收缩)。

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

    对于( ilde Ssupseteq S, ilde Tsubseteq T),如果(Win MC(S,T))( ilde Win MC( ilde S, ilde T)),那么(Wcup ilde Win MC( ilde S, ilde T))

    证明:先证明一个式子:

    [mc(W,W^c)+mc( ilde W, ilde W^c)geq mc(Wcup ilde W,(Wcup ilde W)^c)+mc(Wcap ilde W,(Wcap ilde W)^c) ]

    左边比右边多了(Wsetminus ilde W)( ilde Wsetminus W)之间的边的贡献,因此成立。

    考虑(Wcap ilde W)是一种对于((S,T))合法的真实分割情况,而(W)是最优的情况,因此(mc(Wcap ilde W,(Wcap ilde W)^c)geq mc(W,W^c))
    同样(Wcup ilde W)是一种对于(( ilde S, ilde T))合法的真实分割情况,而( ilde W)是最优的情况,因此(mc(Wcup ilde W,(Wcup ilde W)^c)geq mc( ilde W, ilde W^c))

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

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

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

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

    代码:

    #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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    为Qtcreator 编译的程序添加管理员权限
    热备,冷备,云备
    最近面试java后端开发的感受:如果就以平时项目经验来面试,通过估计很难——再论面试前的准备
    进入2012 -- 回顾我走过的编程之路
    为什么中国程序员水平一直上不了层次?无非是这些原因!
    我是如何失去团队掌控的?
    后端开发甩锅奥义
    一个线程oom,进程里其他线程还能运行吗?
    架构师必备,带你弄清混乱的JAVA日志体系!
    IDEA一定要改的八条配置
  • 原文地址:https://www.cnblogs.com/Mr-Spade/p/13830332.html
Copyright © 2020-2023  润新知