YBTOJ:炮塔攻击(网络流)

题目描述

小明最近在玩一款很好玩的游戏,游戏规则是这样的:

有一个 n ∗ m n*m nm的地图,地图上的每一个位置要么是空地,要么是炮塔,要么是一些敌人,小明需要操纵炮塔攻击敌人。

攻击方法是:对于每个炮塔,游戏系统已经给出它可以瞄准的方向(上、下、左、右其中一个),小明需要选择它的攻击位置,每一个炮塔只能够攻击一个位置,炮塔只能够向着它的瞄准方向上的某个位置发动攻击,当然炮塔也可以不进行攻击。炮塔威力强大,它可以且仅可以消灭目标位置上所有敌人。

出于安全考虑,游戏系统已经保证不存在一个炮塔能够瞄准另外一个炮塔,即对于任意一个炮塔,它所有可能的攻击位置上不存在另外一个炮塔。而且,如果把炮塔的起点和终点称为炮弹的运行轨迹,那么系统不允许两条轨迹相交(包括起点和终点)。

现在,选定目标位置以后,每一个炮塔同时开炮,你要告诉小明,他最多可以干掉多少敌人。

解析

这题的关键还是在建图上
先求出每个炮塔能打到的最大值,加在一起设为tot
然后考虑冲突的情况
由于炮塔互相达不到,因此冲突一定是横向与竖向的冲突
把每个点割成横点和竖点两个点,横点向竖点连一条inf的边
然后对与每个横向炮塔,连一条s到炮塔的inf的边
然后从这个炮塔一直往炮弹方向相邻地连边,边权为 (炮塔达到的最大值) - (边的起点格子的权值)
竖向炮塔同理,连向t点,边的方向全反过来balabala…
这样不合法的情况就会使s与t联通
考虑割掉一条 ( i , j ) (i,j) (i,j) ( i , j + 1 ) (i,j+1) (i,j+1)的边,含义就是对应的炮塔改为攻击 ( i , j ) (i,j) (i,j)的敌人,并损失相应的代价
最后的答案就是 tot-最小割

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5050;
const int M=1e9;
ll read(){
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();};
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int n,m,s,t;
struct node{
	int to,nxt;ll cap;
}p[3000500];
int fi[N],cnt;
void addline(int x,int y,ll cap){
	p[++cnt]=(node){y,fi[x],cap};fi[x]=cnt;
	p[++cnt]=(node){x,fi[y],0};fi[y]=cnt;
}
int col[N],cur[N];
queue<int>q;
int bfs(){
	memset(col,0,sizeof(col));col[s]=1;
	q.push(s);
	while(!q.empty()){
		int now=q.front();q.pop();
		for(int i=cur[now]=fi[now];~i;i=p[i].nxt){
			int to=p[i].to;
			if(col[to]||!p[i].cap) continue;
			col[to]=col[now]+1;
			q.push(to);
		}
	}
	return col[t];
}
ll dfs(int x,ll lim){
	if(x==t||!lim) return lim;
	ll res=0;
	for(int &i=cur[x];~i;i=p[i].nxt){
		int to=p[i].to;
		if(!p[i].cap||col[to]!=col[x]+1) continue;
		ll add=dfs(to,min(lim,p[i].cap));
		lim-=add;res+=add;
		p[i].cap-=add;p[i^1].cap+=add;
		if(!lim) break;
	}
	if(lim) col[x]=-1;
	return res;
}
ll dinic(){
	ll res=0;
	while(bfs()){
		while(ll tmp=dfs(s,2e15)) res+=tmp;
	}
	return res;
}
int vis[N];
void find1(int x){
	if(vis[x]) return;
	vis[x]=1;
	for(int i=fi[x];~i;i=p[i].nxt){
		if(!p[i].cap) continue;
		find1(p[i].to);
	}
	return;
}
void find2(int x){
	if(vis[x]) return;
	vis[x]=2;
	for(int i=fi[x];~i;i=p[i].nxt){
		if(!p[i^1].cap) continue;
		find2(p[i].to);
	}
	return;
}
#define id(x,y) ((x-1)*m+y)
int num,x,tot;
int dx[5]={0,-1,1,0,0},dy[5]={0,0,0,-1,1};
inline bool exi(int x,int y){
	return x>=1&&x<=n&&y>=1&&y<=m;
}
int a[55][55],mx[2505];
struct tower{
	int x,y,op;
	int mx,xx,yy;
}o[2505];
#define heng(x,y) ((x-1)*m+y)
#define shu(x,y) ((x-1)*m+y+n*m)
int main(){
	memset(fi,-1,sizeof(fi));cnt=-1;
	n=read();m=read();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			x=read();
			if(x>=0) a[i][j]=x;
			else{
				x=-x;
				o[++num]=(tower){i,j,x,0,i,j};
			}
		}
	}//printf("ok");
	for(int i=1;i<=num;i++){
		int x=o[i].x,y=o[i].y,op=o[i].op;
		while(x>=1&&x<=n&&y>=1&&y<=m){
			x+=dx[op];y+=dy[op];
			if(a[x][y]>o[i].mx){
				o[i].mx=a[x][y];
				o[i].xx=x;o[i].yy=y;
			}
		}
		tot+=o[i].mx;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) addline(heng(i,j),shu(i,j),2e15);
	}
	s=2*n*m+1;t=s+1;
	for(int i=1;i<=num;i++){
		int x=o[i].x,y=o[i].y,op=o[i].op;
		int xx=o[i].xx,yy=o[i].yy,mx=o[i].mx;
		//a[x][y]=2e9;
		if(op<=2){
			addline(shu(x,y),t,2e15);
			while(xx!=x||yy!=y){
			//	printf("(%d %d)->(%d %d) len=%d\n",xx,yy,xx-dx[op],yy-dy[op],a[xx-dx[op]][yy-dy[op]]);
				addline(shu(xx,yy),shu(xx-dx[op],yy-dy[op]),mx-a[xx-dx[op]][yy-dy[op]]);
				xx-=dx[op];yy-=dy[op];
			}
		}
		else{
			addline(s,heng(x,y),2e15);
			while(x!=xx||y!=yy){
			//	printf("(%d %d)->(%d %d) len=%d\n",x,y,x+dx[op],y+dy[op],a[x][y]);
				addline(heng(x,y),heng(x+dx[op],y+dy[op]),mx-a[x][y]);
				x+=dx[op];y+=dy[op];
			}
		}
	}
	printf("%d\n",tot-dinic());
    return 0;
}
/*
3 2 1 3
1 2 10
2 3 10
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值