洛谷OJ P1263 宫廷守卫

题目描述:给出一个n乘m的网格图,一共有3种格子:空地,陷阱和墙。你只能在空地上放守卫,而且放下的守卫不能存在有两个守卫在同一行或同一列,而且没有墙隔着的情况。(1≤n,m≤200)

100%做法:

这题没有部分分,所以只能直接将100分的做法:这题一开始感觉像是玄学dp,但搞不出来,就往网络流的放向上去想了:考虑这样一个图:

5 6
0 2 1 0 1 0 
1 1 1 0 1 1 
0 1 0 1 1 1 
1 0 2 1 0 0 

0 1 0 1 0 1 


其中白色代表空地,褐色五边形代表墙,红色爆炸型代表陷阱。

我们可以根据原图建出网络流的图,考虑将行和列分开考虑,根据这个图可以发现,第1行中,1-1列只能有一个守卫,2-6列只能有一个守卫,第2行1-6列只能有一个守卫......以此类推,列也类似,而我们考虑在第(i,j)个点放守卫,那么就要同时满足行和列的限制。所以可以建两排点,第一排点代表行的限制,第二排点表示列的限制,源点给每个行限制1的流量,每个列的限制只有一条1流量的边连向汇点当一个空格为(i,j)时就将对应的行限制向对应的列限制连一条流量为1的边。上图样例如下图建图:


这样,跑一遍dinic是完全不虚的。最大流即能放多少个守卫,额第1层点连向第2层点的边如果流掉了就是选了2层点对应的行和列上的点。

                                  

                                                                     推荐番:《fate stay night》

上代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
struct data
{int type,i;};
data a[200005];
int n,m,h,t,dis[200005],cur[200005],pp[200005],ope[200005],v[200005],to[200005],next[200005],www;
int S,T,p,x,y,val,ans,last,bian,mapp[205][205],hang[205][205],lie[205][205],pand[200005];
bool bfs(int k)
{
	for (int i=1;i<=bian;i++)dis[i]=0;dis[S]=0;dis[T]=0;
	for (int i=1;i<=bian;i++)cur[i]=pp[i];cur[S]=pp[S];cur[T]=pp[T];
	ope[1]=k;h=1;t=1;dis[k]=1;
	while (h<=t)
	{
		int pu=pp[ope[h]];
		while (pu>-1)
		{
			if (v[pu]>0 && dis[to[pu]]==0)
			{
				t++;ope[t]=to[pu];dis[to[pu]]=dis[ope[h]]+1;
				if (to[pu]==T){return true;}
			}
			pu=next[pu];
		}
		h++;
	}
	return false;
}
int dfs(int a,int num)
{
	if (a==T)return num;
	int pu=cur[a],ff=0,fff=0;
	while (pu>-1)
	{
		if (num==0)return fff;
		if (v[pu]>0 && dis[to[pu]]==dis[a]+1)
		{
			 ff=dfs(to[pu],min(num,v[pu]));
			 fff+=ff;
			 num-=ff;
			 v[pu]-=ff;v[pu^1]+=ff;
		}
		pu=next[pu];cur[a]=pu;
	}
	return fff;
}
int main()
{
    cin>>n>>m;p=-1;S=100000;T=100001;pp[S]=-1;pp[T]=-1;
    for (int i=1;i<=n*m*2;i++)pp[i]=-1;
    for (int i=1;i<=n;i++)
    {
    	for (int j=1;j<=m;j++)
    	scanf("%d",&mapp[i][j]);
	}
	for (int i=1;i<=n;i++)
	{
		last=1;
		for (int j=1;j<=m;j++)
		{
			{
				if (mapp[i][j]==2 && last<j)
				{
					bian++;
					for (int k=last;k<j;k++)
					{
						hang[i][k]=bian;
					}
					last=j+1;
					a[bian].type=1;a[bian].i=i;
				}
				if (mapp[i][j]==2)last=j+1;
			}
		}
		if (last<=m)
		{
		bian++;
		for (int j=last;j<=m;j++)hang[i][j]=bian;
		a[bian].type=1;a[bian].i=i;
	    }
	}
	www=bian;
	for (int i=1;i<=m;i++)
	{
		last=1;
		for (int j=1;j<=n;j++)
		{
			{
				if (mapp[j][i]==2 && last<j)
				{
					bian++;
					for (int k=last;k<j;k++)
					{
						lie[k][i]=bian;
					}
					last=j+1;
					a[bian].type=2;a[bian].i=i;
				}
				if (mapp[j][i]==2)last=j+1;
			}
		}
		if (last<=n)
		{
		bian++;
		for (int j=last;j<=n;j++)lie[j][i]=bian;
		a[bian].type=0;a[bian].i=i;
	    }
	}
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
		{
			if (mapp[i][j]==0)
			{
				x=hang[i][j];y=lie[i][j];
				p++;to[p]=y;v[p]=1;next[p]=pp[x];pp[x]=p;
				p++;to[p]=x;v[p]=0;next[p]=pp[y];pp[y]=p;
				if (pand[x]==0)
				{
				    pand[x]=1;
				    p++;to[p]=x;v[p]=1;next[p]=pp[S];pp[S]=p;
				    p++;to[p]=S;v[p]=0;next[p]=pp[x];pp[x]=p;
				}
					if (pand[y]==0)
				{
				    pand[y]=1;
				    p++;to[p]=T;v[p]=1;next[p]=pp[y];pp[y]=p;
				    p++;to[p]=y;v[p]=0;next[p]=pp[T];pp[T]=p;
				}
			}
		}
	}
	while (bfs(S)){ans+=dfs(S,1000000007);}
	printf("%d\n",ans);
	for (int i=1;i<=www;i++)
	{
		int pu=pp[i];
		while (pu>-1)
		{
			if (v[pu]==0 && to[pu]!=S){printf("%d %d\n",a[i].i,a[to[pu]].i);}
			pu=next[pu];
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值