bzoj 2150

蜜汁互测题...

有一个思路是显而易见的:爆搜!

期望得分30

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
bool maps[55][55];
bool used[55][55];
char s[55];
int m,n,r,c;
int cnt=1;
bool check(int x,int y)
{
  if(x>0&&x<=n&&y>0&&y<=m&&maps[x][y]&&!used[x][y])
    {
      return 1;
    }
  return 0;
}
int ans=0x3f3f3f3f;
void dfs(int x,int y,int tempcnt)
{
  int x0=x+r;
  int y0=y+c;
  bool flag1=0;
  if(check(x0,y0))
    {
      used[x0][y0]=1;
      dfs(x0,y0,tempcnt);
      used[x0][y0]=0;
      flag1=1;
    }
  x0=x+c;
  y0=y+r;
  bool flag2=0;
  if(check(x0,y0))
    {
      used[x0][y0]=1;
      dfs(x0,y0,tempcnt);
      used[x0][y0]=0;
      flag2=1;
    }
  x0=x+c;
  y0=y-r;
  bool flag3=0;
  if(check(x0,y0))
    {
      used[x0][y0]=1;
      dfs(x0,y0,tempcnt);
      used[x0][y0]=0;
      flag3=1;
    }
  x0=x+r;
  y0=y-c;
  bool flag4=0;
  if(check(x0,y0))
    {
      used[x0][y0]=1;
      dfs(x0,y0,tempcnt);
      used[x0][y0]=0;
      flag4=1;
    }
  if(!flag1&&!flag2&&!flag3&&!flag4)
    {
      bool flag5=0;
      for(int i=1;i<=n;i++)
	{
	  for(int j=1;j<=m;j++)
	    {
	      if(check(i,j))
		{
		  used[i][j]=1;
		  dfs(i,j,tempcnt+1);
		  used[i][j]=0;
		  flag5=1;
		  break;
		}
	    }
	  if(flag5)
	    {
	      break;
	    }
	}
      if(!flag5)
	{
	  ans=min(ans,tempcnt);
	}
    }
}
int main()
{
  freopen("legion.in","r",stdin);
  freopen("legion.out","w",stdout);
  scanf("%d%d%d%d",&n,&m,&r,&c);
  for(int i=1;i<=n;i++)
    {
      scanf("%s",s+1);
      for(int j=1;j<=m;j++)
	{
	  if(s[j]=='.')
	    {
	      maps[i][j]=1;
	    }else
	    {
	      maps[i][j]=0;
	    }
	}
    }
  bool flag=0;
  for(int i=1;i<=n;i++)
    {
      for(int j=1;j<=m;j++)
	{
	  if(check(i,j))
	    {
	      used[i][j]=1;
	      dfs(i,j,1);
	      flag=1;
	      break;
	    }
	}
      if(flag)
	{
	  break;
	}
    }
  printf("%d\n",ans);
  return 0;
}

然后考虑正解

我们发现,最坏情况就是每个点都派驻军队,所以答案至多是“.”的数目

而且,每个点都至多只有一个入度和一个出度,所以我们可以将每个点拆成两个点,一个作为入点,一个作为出点,然后所有图上能到达的点由出点向入点建图

这样整个图就形成了一个二分图

然后在整个图上跑二分图匹配即可

答案即为“.”点数-二分图最大匹配数

稍微证明一下:我们的目的是最大化有入边的点的数量,那我们仅需让入点能更多的被匹配上即可

那也就是二分图匹配喽

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
struct Edge
{
	int next;
	int to;
}edge[10005];
int head[2505];
int maps[55][55];
char s[55];
bool used[6005];
int f[6005];
int n,m,r,c;
int cnt=1,cot;
queue <int> Q;
bool check(int x,int y)
{
	if(x>0&&x<=n&&y>0&&y<=m&&maps[x][y])
	{
		return 1;
	}
	return 0;
}
void init()
{
	memset(head,-1,sizeof(head));
	cnt=1;
}
void add(int l,int r)
{
	edge[cnt].next=head[l];
	edge[cnt].to=r;
	head[l]=cnt++;
}
int po(int x,int y)
{
	return (x-1)*m+y; 
}
int ppo(int x,int y)
{
	return (x-1)*m+y+n*m;
}
bool dfs(int x)
{
	for(int i=head[x];i!=-1;i=edge[i].next)
	{
		int to=edge[i].to;
		if(used[to])
		{
			continue;
		}
		used[to]=1;
		if(!f[to]||dfs(f[to]))
		{
			f[to]=x;
			return 1;
		}
	}
	return 0;
}
int hungary()
{
	int ret=0;
	while(!Q.empty())
	{
		memset(used,0,sizeof(used));
		int u=Q.front();
		Q.pop();
		if(dfs(u))
		{
			ret++;
		}
	}
	return ret;
}
int main()
{
	freopen("legion.in","r",stdin);
	freopen("legion.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&r,&c);
	init();
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		for(int j=1;j<=m;j++)
		{
			if(s[j]=='.')
			{
				maps[i][j]=1;
				Q.push(po(i,j));
				cot++;
			}else
			{
				maps[i][j]=0;
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			int x0=i+r;
			int y0=j+c;
			if(check(x0,y0))
			{
				add(po(i,j),ppo(x0,y0));
			}
			x0=i+r;
			y0=j-c;
			if(check(x0,y0))
			{
				add(po(i,j),ppo(x0,y0));
			}
			x0=i+c;
			y0=j+r;
			if(check(x0,y0))
			{
				add(po(i,j),ppo(x0,y0));
			}
			x0=i+c;
			y0=j-r;
			if(check(x0,y0))
			{
				add(po(i,j),ppo(x0,y0));
			}
		}
	}
	printf("%d\n",cot-hungary());
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值