zoukankan      html  css  js  c++  java
  • [CQOI2012]交换棋子(拆点+费用流)

    题目

    (注意相邻交换指的是八连通)

    思路

    显然可以将黑白交换看做只有黑色棋子在空棋盘上移动

    交换限制交给流量,那么有代价自然就是费用流了

    考虑直接将两个相邻格子连起来,由于交换次数是相对单个格子而言的,流量无法确定
    既然交换次数是单个格子的属性,自然可以考虑到拆点来表示其属性

    考虑将一个格子拆成入点和出点,中间连一条边,流量为交换次数;但是想象一个黑色棋子从1->2->3,那么中间格子用了两次,两边格子只用了一次,这个路径就没法用两个点表示了

    继续拆,拆成三个点,入点出点和中间点,中间点即为黑色格子所在位置,移动操作相当于中间点->出点->入点->中间点;我们在中间点->出点,入点->中间点这两个地方限流;
    发现如果一个位置初始是黑点,最终是白点,那么出去的流量显然为进来的流量+1,其余同理

    所以将交换次数平分给进来和出去两个地方,分情况讨论“1”的分配

    其他的边很好想,连好后跑一次MCMF即可

    #include<bits/stdc++.h>
    #define N 2005
    #define M 200005
    using namespace std;
    const int inf = 100000000;
    int n,m,s,t,maxflow,mincost;
    int pre[N],preedge[N],flow[N];
    int exist[N],dis[N];
    int dox[8]={-1,-1,-1,0,0,1,1,1};
    int doy[8]={-1,0,1,-1,1,-1,0,1};
    char st[25][25],ed[25][25],lim[25][25];
    struct Edge
    {
    	int next,to,flow,dis;
    }edge[M<<1];int head[N],cnt=1;
    void add_edge(int from,int to,int flow,int dis)
    {
    	edge[++cnt].next=head[from];
    	edge[cnt].to=to;
    	edge[cnt].flow=flow;
    	edge[cnt].dis=dis;
    	head[from]=cnt;
    }
    void add(int from,int to,int flow,int dis)
    {
    	add_edge(from,to,flow,dis);
    	add_edge(to,from,0,-dis);
    }
    int get_id(int x,int y,int c) { return n*m*c + (x-1)*m + y; }
    bool spfa()
    {
    	memset(dis,50,sizeof(dis));
    	memset(flow,50,sizeof(flow));
    	queue<int> q; 
    	dis[s]=0; q.push(s); exist[s]=1;
    	pre[t]=-1;
    	while(!q.empty())
    	{
    		int u=q.front(); q.pop(); exist[u]=0;
    		for(int i=head[u];i;i=edge[i].next)
    		{
    			int v=edge[i].to;
    			if(edge[i].flow && dis[v]>dis[u]+edge[i].dis)
    			{
    				pre[v]=u;
    				preedge[v]=i;
    				dis[v]=dis[u]+edge[i].dis;
    				flow[v]=min(flow[u],edge[i].flow);
    				if(!exist[v]) {exist[v]=1;q.push(v);} 
    			}
    		}
    	}
    	return (pre[t] != -1);
    }
    void MCMF()
    {
    	while(spfa())
    	{
    		maxflow += flow[t];
    		mincost += dis[t]*flow[t];
    		int now=t;
    		while(now!=s)
    		{
    			edge[preedge[now]].flow-=flow[t];
    			edge[preedge[now]^1].flow+=flow[t];
    			now=pre[now];
    		}
    	}
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	s=0; t=N-1;
    	for(int i=1;i<=n;++i) scanf("%s",st[i]);
    	for(int i=1;i<=n;++i) scanf("%s",ed[i]);
    	for(int i=1;i<=n;++i) scanf("%s",lim[i]);
    	int sum=0;
    	for(int i=1;i<=n;++i)
    	{
    		for(int j=1;j<=m;++j)
    		{
    			//入点出点 
    			if(st[i][j-1]=='1') ++sum,add(s,get_id(i,j,1),1,0);
    			if(ed[i][j-1]=='1') add(get_id(i,j,1),t,1,0);
    			//自己连边
    			int l = (lim[i][j-1]-'0');
    			if(st[i][j-1]=='1' && ed[i][j-1]=='0')
    			{
    				add(get_id(i,j,0),get_id(i,j,1),l/2,1);
    				add(get_id(i,j,1),get_id(i,j,2),(l+1)/2,1);
    			}
    			else if(st[i][j-1]=='0' && ed[i][j-1]=='1')
    			{
    				add(get_id(i,j,0),get_id(i,j,1),(l+1)/2,1);
    				add(get_id(i,j,1),get_id(i,j,2),l/2,1);
    			}
    			else 
    			{
    				add(get_id(i,j,0),get_id(i,j,1),l/2,1);
    				add(get_id(i,j,1),get_id(i,j,2),l/2,1);
    			}
    			//八连通 
    			for(int k=0;k<8;++k)
    			{
    				int dx=dox[k]+i,dy=doy[k]+j;
    				if(dx<1 || dx>n || dy<1 || dy>m) continue;
    				add(get_id(i,j,2),get_id(dx,dy,0),inf,0);
    			}
    		}
    	}
    	MCMF();
    	if(maxflow != sum) cout<<-1<<endl;
    	else cout<<mincost/2<<endl;
    	return 0;
    }
    
  • 相关阅读:
    LeetCode Reverse Integer
    LeetCode 3Sum Closest
    LeetCode Remove Duplicates from Sorted Array
    解决git 远程删除分支之后,vscode本地分支列表没有更新问题
    reduce, filter, map, forEach 使用
    ehcarts tooltip 自定义展示. 展示小图例
    vscode插件,代码块,快捷键 入门
    DorisSQL与MySQL函数对照 差异篇
    Doris与mysql语法对照,差异篇
    功能测试报告模板
  • 原文地址:https://www.cnblogs.com/Chtholly/p/11990205.html
Copyright © 2011-2022 走看看