您的位置:首页 > 其它

【HackerRank】Jumping Rooks(最小费用最大流)

2016-09-07 17:29 447 查看
【HackerRank】Jumping Rooks(最小费用最大流)

题目大意:

一个n*n的地图,’#’为山峰,’.’为可用位置。

要放置k个士兵在’.’上,已知每个士兵会攻击同行和同列中能看到的士兵。

即两个士兵如果在同行或同列,且其最短距离内没有’#’,两个士兵为一对可互相攻击的士兵。

问怎样放置k个士兵,才能让可互相攻击的士兵对数最少,输出最少的互相攻击对数。

网络流阿网络流 最小费阿最小费 没想到阿没想到 好神奇阿好神奇……

不太好想,想到了真心感觉好神奇……

首先’#’会把原图分为好几段横块和好几段竖块。给每个块标号,同时记录每个点所在的横块标号和竖块标号。当然’#’不会被算做一块。

设置一个虚源点和虚汇点。

源点与所有横块相连,连n+1条边,每条边流量为1,花费分别为0~n。

所有竖块连到汇点,连n+1条边,每条边流量为1,花费分别为0~n。

遍历图中每一个点,’#’则跳过,对于’.’的点(i,j),所在横块和竖块连接一条边,流量1,费用0。这样图就建好了。

跑法的话,k次最小费,因为保证k <= 可用点数量,所以每次肯定能跑出来一条路。流量肯定是1。此外,这条路一定是源点->横块->竖块->汇点这种模式,其实也就是对应放置了一个士兵。

源点->横块的费用其实就是该横块已有的士兵数,因为源点到该横块的边是费用递增的,保证最小费的话,当前如果是第i小费用的边,该士兵肯定是该横块上第i个士兵,一样的,攻击到的兵数为i-1,也就是该边的费用了。

对于竖块->汇点一样的。

同时图上每个点(i,j)仅对应一条横块到竖块的点,那么就保证了每个点最多放一个士兵。

代码如下:

#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <ctime>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)

using namespace std;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-8;
const int MAXK = 5511;
const int MAXM = 6255555;

struct Edge
{
int v,cap,cost,next;
Edge(){}
Edge(int _v,int _cap,int _cost,int _next):v(_v),cap(_cap),cost(_cost),next(_next){}
};

Edge eg[MAXM];
char mp[55][55];
int head[MAXK];
int R[55][55],C[55][55];
int tp,s,t;
int pre[MAXK];
int dis[MAXK];
bool vis[MAXK];

void Add(int u,int v,int cap,int cost)
{
eg[tp] = Edge(v,cap,cost,head[u]);
head[u] = tp++;
eg[tp] = Edge(u,0,-cost,head[v]);
head[v] = tp++;
}

int spfa()
{
memset(vis,0,sizeof(vis));
memset(dis,INF,sizeof(dis));
memset(pre,-1,sizeof(pre));
queue <int> q;
q.push(s);
dis[s] = 0;

int u,v,w,c;
while(!q.empty())
{
u = q.front();
q.pop();
vis[u] = 0;

for(int i = head[u]; i != -1; i = eg[i].next)
{
v = eg[i].v;
c = eg[i].cost;

if(eg[i].cap && dis[u]+c < dis[v])
{
dis[v] = dis[u]+c;
pre[v] = i;
if(!vis[v])
{
vis[v] = 1;
q.push(v);
}
}
}
}

u = t;

while(u != s)
{
eg[pre[u]].cap--;
eg[pre[u]^1].cap++;
u = eg[pre[u]^1].v;
}

return dis[t];
}

int main()
{
//fread("");
//fwrite("");

int n,k;

scanf("%d%d",&n,&k);

for(int i = 0; i < n; ++i)
scanf("%s",mp[i]);

memset(head,-1,sizeof(head));
tp = 0;

int id = 0;

//给横块标号
for(int i = 0; i < n; ++i)
{
int j = 0;
while(j < n)
{
if(mp[i][j] == '#') ++j;
else
{
id++;
while(j < n && mp[i][j] != '#') R[i][j++] = id;
}
}
}

int cntr = id;

//给竖块标号
for(int j = 0; j < n; ++j)
{
int i = 0;
while(i < n)
{
if(mp[i][j] == '#') ++i;
else
{
id++;
while(i < n && mp[i][j] != '#') C[i++][j] = id;
}
}
}

s = 0;
t = id+1;

//源点与横块 n+1条边
for(int i = 1; i <= cntr; ++i)
for(int j = 0; j <= n; ++j)
Add(0,i,1,j);

//竖块与汇点 n+1条边
for(int i = cntr+1; i <= id; ++i)
for(int j = 0; j <= n; ++j)
Add(i,t,1,j);

//每个可放位置对应的边 横块->竖块
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
{
if(mp[i][j] == '#') continue;
Add(R[i][j],C[i][j],1,0);
}

int ans = 0;

//k条路(k个士兵)
while(k--) ans += spfa();

printf("%d\n",ans);

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: