[清华集训2017]无限之环(网络流)

很妙的一道题

对于每个格子,它合法与否,只跟它上下左右的相邻格子有关,所以可以想到黑白染色
(用 (i,j) 表示 i 行 j 列的格子,我把 (i+j) %2 == 0 的格子染成白色,把(i+j)%2 == 1 的格子染成黑色)

关键是怎么描述旋转操作

我朴实的想法是给每个格子建四个点,每个点代表格子可通过旋转达到的一种状态,从格子的初始状态向格子其它状态连边,然后把格子间可以匹配的状态连起来,再然后……就没有然后了

看了题解,真的被惊到了
还是把每个格子拆成4个节点,对应四个方向上的接口
从源点向白格的接口连流量上界1,费用0的边
从黑格的接口向汇点连流量上界1,费用0的边
从黑格的接口向可以匹配的白格的接口连流量上界1,费用0的边
然后旋转操作,就可以通过一种神奇方式描述出来:

	A
	
D	O	B

	C

把这个看成一个白格(黑格类似,只是连边方向相反)
边(u->v,f,w)代表从 u 到 v,流量上界 f,费用 w 的边
下文描述的旋转方向均为顺时针
1.此格有1个接口:

	A
	|
D	O	B

	C

(A->B ,1,1)) 对应转90度
(A->C, 1,2)) 对应转180度
(A->D ,1,1)) 对应转270度
2.此格有2个接口:
情况1:

	 A
	 |
D	 O —— B

	 C

(A->C, 1,1)) 对应转90度
(B->D ,1,1)) 对应转270度
(A->C, 1,1))+(B->D ,1,1)) 对应转180度
情况2:

	A
	|
D	O	B
	|
	C

不能转,忽略
3.此格有3个接口:

	 A
	 |
D 	 O —— B
	 |
	 C

(A->D ,1,1)) 对应转270度
(B->D, 1,2)) 对应转180度
(C->D ,1,1)) 对应转90度
4.此格有4个接口:

	 A
	 |
D —— O —— B
	 |
	 C

转了也没区别,忽略

建完图后跑最小费用最大流即可

如果我表达不清的话,这是样例1的建图,很丑,凑合着看吧
在这里插入图片描述
图上标的数代表费用,没标的边默认费用为0,所有边的流量上界默认为1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int inf=0x7fffffff;
const int N=8100;
const int M=200010;
struct Edge{
    
    
	int u,v,f,w,nxt;
}edge[M<<1];
int s,t,head[N],cnt,maxflow,mincost,dis[N],inque[N],pre[N];
queue<int> q;
int n,m,id[2005][2005][4],num,gr[2005][2005],tot;
void add(int u,int v,int f,int w){
    
    
	edge[cnt].u=u;edge[cnt].v=v;edge[cnt].f=f;edge[cnt].w=w;edge[cnt].nxt=head[u];head[u]=cnt++;
	edge[cnt].u=v;edge[cnt].v=u;edge[cnt].f=0;edge[cnt].w=-w;edge[cnt].nxt=head[v];head[v]=cnt++;
}
bool spfa(){
    
    
	memset(dis,0x7f,sizeof(dis));
	memset(inque,0,sizeof(inque));
	memset(pre,-1,sizeof(pre));
	dis[s]=0;
	q.push(s);inque[s]=1;
	while(!q.empty()){
    
    
		int u=q.front();
		q.pop();inque[u]=0;
		for(int i=head[u];i!=-1;i=edge[i].nxt){
    
    
			int v=edge[i].v;
			if(edge[i].f>0&&dis[v]>dis[u]+edge[i].w){
    
    
				dis[v]=dis[u]+edge[i].w;
				pre[v]=i;
				if(!inque[v]){
    
    
					q.push(v);inque[v]=1;
				}
			}
		}
	} 
	if(pre[t]!=-1) return 1;
	return 0;
}
void EK(){
    
    
	int flow;
	while(spfa()){
    
    
		flow=inf;
		int x=pre[t];
		while(x!=-1){
    
    
			flow=min(edge[x].f,flow);
			x=pre[edge[x].u];
		}
		x=pre[t];
		while(x!=-1){
    
    
			edge[x].f-=flow;
			edge[x^1].f+=flow;
			mincost+=flow*edge[x].w;
			x=pre[edge[x].u];
		}
		maxflow+=flow;
	}
}
int main(){
    
    
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int k=0;k<4;k++)
				id[i][j][k]=++num;
	s=++num,t=++num;		
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++){
    
    
			scanf("%d",&gr[i][j]);
			int tt=0,pos[5]={
    
    -1,-1,-1,-1,-1},npos=-1;
			for(int k=0;k<4;k++){
    
    
				if(gr[i][j]&(1<<k)){
    
    
					tot++;pos[++tt]=k;
					if((i+j)%2==0) add(s,id[i][j][k],1,0);
					else add(id[i][j][k],t,1,0);
				}
				else npos=k;
			}
			if(tt==1){
    
    
				if((i+j)%2==0){
    
    
					add(id[i][j][pos[1]],id[i][j][(pos[1]+1)%4],1,1);
					add(id[i][j][pos[1]],id[i][j][(pos[1]+2)%4],1,2);
					add(id[i][j][pos[1]],id[i][j][(pos[1]+3)%4],1,1);
				}
				else{
    
    
					add(id[i][j][(pos[1]+1)%4],id[i][j][pos[1]],1,1);
					add(id[i][j][(pos[1]+2)%4],id[i][j][pos[1]],1,2);
					add(id[i][j][(pos[1]+3)%4],id[i][j][pos[1]],1,1);
				}
			}
			else if(tt==2){
    
    
				if(pos[2]-pos[1]==2) continue;
				if((i+j)%2==0){
    
    
					add(id[i][j][pos[1]],id[i][j][(pos[1]+2)%4],1,1);
					add(id[i][j][pos[2]],id[i][j][(pos[2]+2)%4],1,1);
				}
				else{
    
    
					add(id[i][j][(pos[1]+2)%4],id[i][j][pos[1]],1,1);
					add(id[i][j][(pos[2]+2)%4],id[i][j][pos[2]],1,1);
				}
			}
			else if(tt==3){
    
    
				if((i+j)%2==0){
    
    
					add(id[i][j][(npos+1)%4],id[i][j][npos],1,1);
					add(id[i][j][(npos+2)%4],id[i][j][npos],1,2);
					add(id[i][j][(npos+3)%4],id[i][j][npos],1,1);
				}
				else{
    
    
					add(id[i][j][npos],id[i][j][(npos+1)%4],1,1);
					add(id[i][j][npos],id[i][j][(npos+2)%4],1,2);
					add(id[i][j][npos],id[i][j][(npos+3)%4],1,1);
				}
			}
			else continue;
		}
	}
	//一开始这一块的建图错了 
	int dx[]={
    
    -1,0,1,0};
	int dy[]={
    
    0,1,0,-1};
	for(int i=1;i<=n;i++){
    
    
        for(int j=1;j<=m;j++){
    
    
            if((i+j)%2==0){
    
    
            	for(int k=0;k<=3;k++){
    
    
                    int x=i+dx[k],y=j+dy[k];
                    if(x<1||x>n||y<1||y>m) continue;
                    add(id[i][j][k],id[x][y][(k+2)%4],1,0);
                }
			}
    	}
	}
	EK();
	if(tot%2!=0||maxflow!=tot/2) printf("-1\n"); //注意还要判断tot%2!=0的情况 
	else printf("%d\n",mincost);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Emma2oo6/article/details/113424182
今日推荐