LA 4949 Risk 最大流

Risk

Risk is a board game played on a world map. This world is divided intoregions by borders. Each region is controlled by a player (either youor one of your opponents). Any region that you control contains apositive number of your armies.In each turn, you are allowed to move your armies. Each of your armiesmay either stay where it is, or move from a region to a borderingregion under your control. The movements are performed one by one, inan order of your choice. At all times, each region must contain atleast one army.For strategic purposes, it is important to move your armies to regionsthat border your opponents' regions and minimize the number of armieson your regions that are totally surrounded by other regions underyour control. You want your weakest link, i.e., the border region withthe minimum number of armies, to be as strong as possible. What is themaximum number of armies that can be placed on it after one turn?

Input

On the first line a positive integer: the number of test cases, atmost 100. After that per test case:
  • One line with an integer n (1 ≤ n ≤ 100): the number ofregions.
  • One line with n integers ai (0 ≤ ai ≤ 100): the numberof your armies on each region. A number 0 indicates that a regionis controlled by your opponents, while a positive number indicatesthat it is under your control.
  • n lines with n characters, where each character is either `Y' or`N'. The i-th character of the j-th line is `Y' ifregions i and j border, and `N' otherwise. Thisrelationship is symmetric and the i-th character of the i-thline will always be `N'.
In every test case, you control at least one region, and your opponents control at least oneregion. Furthermore, at least one of your regions borders at least oneof your opponents' regions.

Output

Per test case:
  • One line with an integer: the maximum number of armies on your weakestborder region after one turn of moving.

Sample in- and output

InputOutput
2
3
1 1 0
NYN
YNY
NYN
7
7 3 3 2 0 0 5
NYNNNNN
YNYYNNN
NYNYYNN
NYYNYNN
NNYYNNN
NNNNNNY
NNNNNYN
1
4

传送门:LA 4949 Risk

题目大意:

Risk是一款桌面游戏,上面有N个领域,其中领域之间可能有路相通。其中,每个领域上都有一个整数表示军队数,正整数表示这个是我方领地,0表示这个是敌方领地,且领地关系永远不变。每个回合,你可以将其中任意个我方领地的军队派遣到与之相邻的其他的我方领地,而且必须保证在回合结束以后我方每个领地至少有一支军队。现在你和你的对手在玩这个游戏,并且告诉你当前的军队分布以及领域之间的道路关系(是否连通),问在这个回合的调遣后,与敌方领地相邻的我方领地(下述简称边界领域)中军队数最少的领域最多能达到多少?


题目分析:

先建立超级源汇s、t,army[ ]表示军队数。

首先对于所有的我方领地 i,建边(s,i,army[ i ] - 1)表示所有领地在保证自己的领地至少有一支军队的前提下能向其他领域派遣的最多军队数,对所有的边界领域 i 建边(i,t,x(初始值设为oo))表示每个边界领地至少需要 x 个军队。接下来,对所有的能到达边界领域 j 的我方领域 i 建边(i,j,oo)表示在条件允许下能随便派遣,对所有能到达我方非边界领域 j 的我方领域 i 建边(i,j, 1)表示如果从我方非边界领地可以派遣军队到相邻的我方领地,那么就可以一直传递下去使得最终可以让与边界领域相邻的我方领域派遣最后一支军队(这样就能充分利用资源了)。

最后我们通过二分查找修改连向超级汇点的边的容量,最大流寻找最终结果(满流往上搜,否则往下搜,满流同时记录答案),最终结果及二分搜到的答案 + 1(别忘了一开始边界军队的镇守本地的军队要加上)。

PS:在写题解之前,我连算法的正确性都不敢保证,随便提交了一发直接1A 23333,果然人品很重要,不过考试估计要挂T   T

代码如下:


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define clear(A, X) memset (A, X, sizeof A)
#define copy(A, B) memcpy (A, B, sizeof A)
using namespace std;

const int maxE = 1000000;
const int maxN = 105;
const int maxM = 60;
const int maxQ = 1000000;
const int oo = 0x3f3f3f3f;

struct Edge {
	int v, c, n, rc;
} edge[maxE];//边组


int adj[maxN], cntE, cntEE;
int Q[maxQ], head, tail;//队列
int d[maxN], cur[maxN], pre[maxN], num[maxN];
int s, t, nv;//s:源点,t:汇点,nv:编号修改的上限
int n;
int army[maxN], border[maxN], number;
char G[maxN][maxN];

void addedge (int u, int v, int c) {//添加边
	edge[cntE].v = v;
	edge[cntE].c = c;
	edge[cntE].rc = c;
	edge[cntE].n = adj[u];
	adj[u] = cntE++;
	
	edge[cntE].v = u;
	edge[cntE].c = 0;
	edge[cntE].rc = 0;
	edge[cntE].n = adj[v];
	adj[v] = cntE++;
}

void rev_bfs () {
	clear (num, 0);
	clear (d, -1);
	d[t] = 0;
	num[0] = 1;
	head = tail = 0;
	Q[tail++] = t;

	while (head != tail) {
		int u = Q[head++];
		for (int i = adj[u]; ~i; i = edge[i].n) {
			int v = edge[i].v;
			if (~d[v]) continue;
			d[v] = d[u] + 1;
			Q[tail++] = v;
			num[d[v]]++;
		}
	}
}

int ISAP() {
	copy (cur, adj);
	rev_bfs ();
	int flow = 0, u = pre[s] = s, i;

	while (d[s] < nv) {
		if (u == t) {
			int f = oo, neck;
			for (i = s; i != t; i = edge[cur[i]].v) {
				if (f > edge[cur[i]].c){
					f = edge[cur[i]].c;
					neck = i;
				}
			}
			for (i = s; i != t; i = edge[cur[i]].v) {
				edge[cur[i]].c -= f;
				edge[cur[i] ^ 1].c += f;
			}
			flow += f;
			u = neck;
		}
		for (i = cur[u]; ~i; i = edge[i].n) if (d[edge[i].v] + 1 == d[u] && edge[i].c) break;
		if (~i)  {
			cur[u] = i;
			pre[edge[i].v] = u;
			u = edge[i].v;
		}
		else {
			if (0 == (--num[d[u]])) break;
			int mind = nv;
			for (i = adj[u]; ~i; i = edge[i].n) {
				if (edge[i].c && mind > d[edge[i].v]) {
					cur[u] = i;
					mind = d[edge[i].v];
				}
			}
			d[u] = mind + 1;
			num[d[u]]++;
			u = pre[u];
		}
	}
	
	return flow;
}

void init () {//初始化
	clear (adj, -1);
	cntE = 0;
}

int solve (int mid) {
	for (int i = 0; i < cntEE; ++ i) edge[i].c = edge[i].rc;
	for (int i = cntEE; i < cntE; i += 2) edge[i].c = mid, edge[i ^ 1].c = 0;
	int ans = ISAP ();
	return ans == number * mid;
}

void work () {
	char x;
	scanf ("%d", &n);
	init();
	s = n; t = n + 1; nv = t + 1;
	number = 0;
	clear (border, 0);
	for (int i = 0; i < n; ++ i) scanf ("%d", &army[i]);
	for (int i = 0; i < n; ++ i) {
		scanf ("%s", G[i]);
		for (int j = 0; j < n; ++ j) {
			if (G[i][j] == 'Y' && army[i] && !army[j]) border[i] = 1;//是边界领地的同时也是我方领地
		}
	}
	for (int i = 0; i < n; ++ i) {
		if(!army[i]) continue;
		addedge (s, i, army[i] - 1);
		for (int j = 0; j < n; ++ j) {
			if (army[i] && G[i][j] == 'Y') {//当前位置为我方领地且与 j 相连
				if (border[j]) addedge (i, j, army[i]);//j 是边界领地
				else if (army[j]) addedge (i, j, 1);//不是边界领地但是是我方领地
			}
		}
	}
	cntEE = cntE;
	for (int i = 0; i < n; ++ i) {
		if (border[i]) addedge (i, t, oo), number++;
	}
	int l = 1, r = 10001, ans = 0;
	while (l < r) {
		int mid = (l + r) >> 1;
		if (!solve(mid)) r = mid;//不满流
		else l = mid + 1, ans = mid;//满流
	}
	printf("%d\n", ans + 1);//二分得到的答案 + 镇守的军队
}

int main() {
	int T;
	for (scanf("%d", &T); T; --T) work();
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值