Input | Output |
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; }