【费用流】「2017 山东一轮集训 Day4」棋盘

题目

求放k个棋子在n*n的棋盘上,最少互相攻击的棋子对数。
能够攻击仅当两旗子在一条直线上,且中间无障碍物。
共有q个询问

题解

不难想到这种棋盘上放棋子的题考得是网络流
积累:按行、纵分别按其对应连通块编号,S->行->纵->T
但明显新一行的点互相攻击的话,随点数的增加,贡献是0,1,2,3这样递增的。
于是我们想到给S->行,纵->T连边时 连多条(val=0,1,2,3…,flow=1)的边
最后 行->纵 连(val=0,flow=1)即可

好像忘了一件事,多个询问,用费用流每次增一条路,预处理出来就行

注意预估边条数和点个数
#include<bits/stdc++.h>
using namespace std;
const int N=6e3+10,M=2e5+10,Q=1e3+10,INF=1e9+7;
int S,T,MAX_FLOW;
int n; 
int head[N],nex[M],to[M],flow[M],val[M],tot=1;
void build(int u,int v,int f,int w)
{
	tot++;nex[tot]=head[u];to[tot]=v;flow[tot]=f;val[tot]= w;head[u]=tot;
	tot++;nex[tot]=head[v];to[tot]=u;flow[tot]=0;val[tot]=-w;head[v]=tot;
}
int mp[55][55];
int bl[2][55][55];
void make_build()
{
	int re0=1,pre0=0;
	for(int j=1;j<=n;j++)
	{
		for(int i=1;i<=n;i++)
		{
			if(mp[i][j]){if(pre0!=1)re0++;pre0=1;}
			else bl[0][i][j]=re0,pre0=0;
		}re0++;pre0=1;
	}
	int re1=1,pre1=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(mp[i][j]){if(pre1!=1)re1++;pre1=1;}
			else bl[1][i][j]=re1,pre1=0;
		}re1++;pre1=1;
	}//pre用来尽量压点个数,但可能没什么用 
	S=0,T=re0+re1+1,MAX_FLOW=T;
	for(int i=1;i<=re0;i++)for(int k=0;k<re1;k++)build(S,i,1,k);
	for(int i=1;i<=re1;i++)for(int k=0;k<re0;k++)build(i+re0,T,1,k);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		if(mp[i][j])continue;
		int fw1=bl[0][i][j],fw2=bl[1][i][j]+re0;
		build(fw1,fw2,1,0);
	}
}
int dis[N],flw[N],pre[N];
bool vis[N];
bool spfa()
{
	memset(vis,0,sizeof(vis));
	memset(pre,0,sizeof(pre));
	memset(flw,0x7f/3,sizeof(flw));
	memset(dis,0x7f/3,sizeof(dis));
	queue<int>q;
	dis[S]=0;vis[S]=1;q.push(S);
	while(!q.empty())
	{
		int u=q.front();q.pop();vis[u]=0;
		for(int i=head[u];i;i=nex[i])
		{
			int v=to[i];
			if(dis[v]>dis[u]+val[i]&&flow[i]>0)
			{
				dis[v]=dis[u]+val[i];
				flw[v]=min(flw[u],flow[i]);
				pre[v]=i;
				if(!vis[v])vis[v]=1,q.push(v);
			}
		}
	}
	return dis[T]<0x3F3F3F3F;
}
int doit()
{
	int tmp=0;
	if(spfa())
	{
		int f=flw[T];
		for(int i=T;i!=S;i=to[pre[i]^1])
		{
			flow[pre[i]]-=f;
			flow[pre[i]^1]+=f;
			tmp+=val[pre[i]]*f;
		}
	}
	return tmp;
}
int q;
int ans[2510];
struct qus{
	int x,id;
	bool operator<(const qus a)const{return x<a.x;}
}qu[Q];
char s[55];
int main()
{
//	freopen("1.in","r",stdin);
//	freopen("1.out","w",stdout);
	int all=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		for(int j=1;s[j];j++)
		if(s[j]=='#')mp[i][j]=1;
		else		 mp[i][j]=0,all++; 
	}
	make_build();
	for(int i=1;i<=all;i++)
	ans[i]=ans[i-1]+doit();
	scanf("%d",&q);
	for(int fw,i=1;i<=q;i++)
	{
		scanf("%d",&fw);
		printf("%d\n",ans[fw]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值