1、搭配飞行员
求二分图最大匹配,建立源点 S S S,汇点 T T T, S S S 向正驾驶员连接容量 1 1 1 的边,副驾驶员向 T T T 连接容量为 1 1 1 的边,可以匹配的正副驾驶员之间连接容量为 1 1 1 的边,求最大流即为最大匹配。
set<int> li, ri;
int main() {
Dinic<int> dn;
int n, m; scanf("%d%d", &n, &m);
int u, v, sp = n + 1, tp = sp + 1;
dn.init(tp);
while(scanf("%d%d", &u, &v) != EOF){
dn.add(u, v, 1);
li.insert(u), ri.insert(v);
}
for(auto x : li) dn.add(sp, x, 1);
for(auto x : ri) dn.add(x, tp, 1);
printf("%d\n", dn.solve(sp, tp));
return 0;
}
2、太空飞行计划
求最大收益即为求最小花费,初始先加上所有收入,再求最小花费。根据冲突关系建边,每个实验 E i E_i Ei 向所需仪器编号连接容量为 i n f inf inf 的边,源点 S S S 向 E i E_i Ei 连接容量为 p i p_i pi 的边,每种仪器 I i I_i Ii 向汇点 T T T 连接容量为 c i c_i ci 的边,则要么进行实验 E i E_i Ei,割去对应仪器集合的所有连向 T T T 的边(即花费),要么割去实验 E i E_i Ei 代表边。求最小割即为最小花费。
至于方案输出,再从源点 S S S 做一次 b f s bfs bfs,则某条边为割边当且仅当两端点一端可达且另一端不可达。
void print(int m){
bfs();
int f = 0;
for(Edge &e : G[sp]){
if(dis[e.v]){
if(f) cout << " "; f = 1;
cout << e.v;
}
}
cout << endl;
f = 0;
for(Edge &e : G[tp]){
if(!dis[tp] != !dis[e.v]){
if(f) cout << " "; f = 1;
cout << e.v - m;
}
}
cout << endl;
}
int main() {
Dinic<int> dn;
int m, n; cin >> m >> n;
int sp = m + n + 1, tp = sp + 1;
dn.init(tp);
int ret = 0;
for(int i = 1; i <= m; ++i){
while(!isdigit(cin.peek())) cin.get();
string s; getline(cin, s);
stringstream ss;
ss << s;
int x; ss >> x;
ret += x;
dn.add(sp, i, x);
while(ss >> x){
dn.add(i, m + x, inf);
}
}
for(int i = 1; i <= n; ++i){
int tmp; cin >> tmp;
dn.add(m + i, tp, tmp);
}
ret -= dn.solve(sp, tp);
dn.print(m);
cout << ret;
return 0;
}
3、最小路径覆盖
有向图最小路径覆盖,将每个点 u u u 拆成 u u u 和 u ′ u' u′,对边 ( u , v ) (u,v) (u,v),连接 u u u 和 v ′ v' v′,则最小路径覆盖数 = = = 点数 − - − 最大匹配。求最大流, d f s dfs dfs 输出方案。
void print(int m){
for(int i = 1; i <= n; ++i) dis[i] = 0;
for(int i = 1; i <= m; ++i){
int p = i; ans.clear();
while(!dis[p]){
dis[p] = 1, ans.pb(p);
for(Edge &e : G[p]){
if(e.v == sp || !G[e.v][e.rev].cap) continue;
p = e.v - m;
}
}
if(!sz(ans)) continue;
for(int x : ans) printf("%d ", x);
printf("\n");
}
}
int main() {
Dinic<int> dn;
int n, m; scanf("%d%d", &n, &m);
int sp = n * 2 + 1, tp = sp + 1;
dn.init(tp);
while(m--){
int u, v; scanf("%d%d", &u, &v);
dn.add(u, n + v, 1);
}
for(int i = 1; i <= n; ++i) dn.add(sp, i, 1), dn.add(n + i, tp, 1);
int ret = n - dn.solve(sp, tp);
dn.print(n);
printf("%d\n", ret);
return 0;
}
4、魔术球
这题有贪心的解法,这里说一下个人的做法。
先预处理出所有数字的后继。二分答案
a
n
s
ans
ans,问题转化为判定能否用
n
n
n 条链覆盖
a
n
s
ans
ans 个球。对每个球
u
u
u 拆点
u
u
u 和
u
′
u'
u′,
u
u
u 向
u
′
u'
u′ 连接容量为
1
1
1、费用为
−
1
-1
−1 的边,
S
S
S 向所有球连容量
1
1
1、费用
0
0
0 的边,所有球向后继连边容量
1
1
1、费用
0
0
0 的边,所有球向
T
T
T 连容量
1
1
1、费用
0
0
0 的边,求最小费用最大流判定。
u p d a t e update update: 这题其实是最小路径覆盖,无需费用流,只需要判定是否最小路径覆盖数 <= n n n,并且也可以不二分,枚举答案每次在残留网络上跑就行。
void dfs(int u){
if(u == tp) return;
if(u != sp) ans.pb(u);
for(auto e : G[u]){
if(e.cot < 0 || e.v < u && G[e.v][e.rev].cap){
--G[e.v][e.rev].cap;
dfs(e.v);
break;
}
}
}
void print(int m, int ret){
sp = G[sp][0].v, tp = G[tp][0].v;
for(int i = 1; i <= m; ++i){
ans.clear();
dfs(sp); int f = 0;
for(int j = 0; j < sz(ans); j += 2){
if(f) cout << " "; f = 1;
cout << ans[j];
}
cout << endl;
}
}
Dinic<int> dn;
vector<int> G[maxn];
int m;
int judge(int mid){
int sp = mid * 2 + 1, tp = sp + 1, st = tp + 1, tt = st + 1;
dn.init(tt);
dn.add(sp, st, m, 0);
dn.add(tt, tp, m, 0);
for(int i = 1; i <= mid; ++i){
for(auto x : G[i]){
if(x > mid) break;
dn.add(mid + i, x, 1, 0);
}
dn.add(i, mid + i, 1, -1);
dn.add(st, i, 1, 0);
dn.add(mid + i, tt, 1, 0);
}
return dn.solve(sp, tp).second == -mid;
}
void init(){
for(int i = 1; i <= 1567; ++i){
for(int j = i + 1; j <= 1567; ++j){
int x = sqrt(i + j + 0.5);
if(x * x == i + j) G[i].pb(j);
}
}
}
int main() {
init();
scanf("%d", &m);
int l = 1, r = 1567, mid, ret;
while(l <= r){
mid = gmid;
if(judge(mid)) l = mid + 1, ret = mid;
else r = mid - 1;
}
printf("%d\n", ret);
judge(ret);
dn.print(m, ret);
return 0;
}
5、圆桌聚餐
S S S 向第 i i i 个单位连容量为 r i r_i ri 的边,第 i i i 张餐桌向 T T T 连容量为 c i c_i ci 的边,所有单位分别向每张餐桌连容量为 1 1 1 的边,求最大流并输出方案。
void print(int m){
for(int i = 1; i <= m; ++i){
for(auto &e : G[i]){
if(e.v == sp || e.cap) continue;
printf("%d ", e.v - m);
}
printf("\n");
}
}
int main() {
Dinic<int> dn;
int m, n; scanf("%d%d", &m, &n);
int sp = m + n + 1, tp = sp + 1;
dn.init(tp);
int sum = 0;
for(int i = 1; i <= m; ++i){
int tmp; scanf("%d", &tmp);
dn.add(sp, i, tmp);
sum += tmp;
}
for(int i = 1; i <= n; ++i){
int tmp; scanf("%d", &tmp);
dn.add(m + i, tp, tmp);
}
for(int i = 1; i <= m; ++i){
for(int j = 1; j <= n; ++j) dn.add(i, m + j, 1);
}
if(dn.solve(sp, tp) != sum) printf("0\n");
else{
printf("1\n");
dn.print(m);
}
return 0;
}
6、最长递增子序列
d p dp dp 求出以 i i i 结尾的最长递增子序列,特判 l i s = 1 lis=1 lis=1。 S S S 向 d p [ i ] = 1 dp[i]=1 dp[i]=1 的点连边, d p [ i ] = l i s dp[i]=lis dp[i]=lis的向 T T T连边,对于 x j x_j xj,向后继状态 x i x_i xi 连边,以上边容量皆为 1 1 1。求最大流则为第二个询问的答案。
对于第三个询问, S S S 向 x 1 x_1 x1 连容量为 i n f inf inf 的边,若 d p [ n ] = l i s dp[n]=lis dp[n]=lis, x n x_n xn 向 T T T 连容量为 i n f inf inf 的边,再求一次最大流。
int a[maxn], dp[maxn], n;
int main() {
Dinic<int> dn;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
int lis = 0;
for(int i = 1; i <= n; ++i){
dp[i] = 1;
for(int j = 1; j < i; ++j){
if(a[j] <= a[i]) dp[i] = max(dp[i], dp[j] + 1);
}
lis = max(lis, dp[i]);
}
printf("%d\n", lis);
if(lis == 1){
printf("%d\n%d\n", n, n);
return 0;
}
int sp = n + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= n; ++i){
if(dp[i] == 1) dn.add(sp, i, 1);
else if(dp[i] == lis) dn.add(i, tp, 1);
for(int j = 1; j < i; ++j){
if(a[j] <= a[i] && dp[j] + 1 == dp[i]) dn.add(j, i, 1);
}
}
int ret = dn.solve(sp, tp);
printf("%d\n", ret);
dn.add(sp, 1, inf);
if(dp[n] == lis) dn.add(n, tp, inf);
ret += dn.solve(sp, tp);
printf("%d\n", ret);
return 0;
}
7、试题库
S S S 向所有题目连容量为 1 1 1 的边,所有题目类型向 T T T 连容量为所需题数的边,对于每道题目,向可以归类的类型连容量为 1 1 1 的边。求最大流并输出方案。
void print(int n, int k){
for(int i = n + 1; i <= n + k; ++i){
printf("%d:", i - n);
for(auto &e : G[i]){
if(e.v == tp || !e.cap) continue;
printf(" %d", e.v);
}
printf("\n");
}
}
int main() {
Dinic<int> dn;
int k, n; scanf("%d%d", &k, &n);
int sp = k + n + 1, tp = sp + 1, sum = 0;
dn.init(tp);
for(int i = 1; i <= k; ++i){
int tmp; scanf("%d", &tmp);
dn.add(n + i, tp, tmp);
sum += tmp;
}
for(int i = 1; i <= n; ++i){
int p; scanf("%d", &p);
while(p--){
int u; scanf("%d", &u);
dn.add(i, n + u, 1);
}
dn.add(sp, i, 1);
}
if(dn.solve(sp, tp) != sum) printf("No Solution!\n");
else dn.print(n, k);
return 0;
}
8、方格取数
先将所有数字相加,转化为求割去的最小代价。将方格黑白染色,利用黑白方格冲突关系建边,求最小割。
const int xp[] = { 0, 0, -1, 1 };
const int yp[] = { -1, 1, 0, 0 };
int n, m, a[maxn][maxn];
int check(int x, int y){
return x >= 1 && x <= n && y >= 1 && y <= m;
}
int getId(int x, int y){
return (x - 1) * m + y;
}
int getCol(int x, int y){
return (x & 1) && (y & 1) || (~x & 1) && (~y & 1);
}
int main() {
Dinic<int> dn;
scanf("%d%d", &n, &m);
int sp = n * m + 1, tp = sp + 1;
dn.init(tp);
int ret = 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
scanf("%d", &a[i][j]);
ret += a[i][j];
}
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
int id = getId(i, j);
if(getCol(i, j)){
dn.add(id, tp, a[i][j]);
continue;
}
dn.add(sp, id, a[i][j]);
for(int k = 0; k < 4; ++k){
int x = i + xp[k], y = j + yp[k];
if(check(x, y)){
dn.add(id, getId(x, y), inf);
}
}
}
}
ret -= dn.solve(sp, tp);
printf("%d\n", ret);
return 0;
}
9、餐巾计划
考虑到每条餐巾有新、旧两种状态,对每条餐巾建立两个点 u u u 和 u ′ u' u′,分别代表旧餐巾和新餐巾。 S S S 向所有 u ′ u' u′ 连边 ( i n f , P ) (inf, P) (inf,P) 表示第 i i i 天可选择以单位价格为 P P P 购入新餐巾, u ′ u' u′ 向 T T T 连边 ( r i , 0 ) (r_i, 0) (ri,0),表示第 i i i 天需要 r i r_i ri 条新餐巾, S S S 向 u u u 连 ( r i , 0 ) (r_i, 0) (ri,0) 的边,表示第 i i i 天产生 r i r_i ri 条旧餐巾, u i u_i ui 向 u i + 1 u_{i+1} ui+1 连边 ( i n f , 0 ) (inf, 0) (inf,0) 表示旧餐巾可以留到下一天, u i u_i ui 向 u i + M ′ u'_{i+M} ui+M′ 连边 ( i n f , F ) (inf, F) (inf,F) 表示快洗 M M M 天后旧餐巾变新餐巾,慢洗同理。最后求最小费用流即可。
int main() {
Dinic<int> dn;
int n, P, M, F, N, S;
scanf("%d%d%d%d%d%d", &n, &P, &M, &F, &N, &S);
int sp = n * 2 + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= n; ++i){
int tmp; scanf("%d", &tmp);
dn.add(sp, i, tmp, 0);
dn.add(sp, n + i, inf, P);
dn.add(n + i, tp, tmp, 0);
}
for(int i = 1; i <= n; ++i){
if(i < n) dn.add(i, i + 1, inf, 0);
if(i + M <= n) dn.add(i, n + i + M, inf, F);
if(i + N <= n) dn.add(i, n + i + N, inf, S);
}
int ret = dn.solve(sp, tp).second;
printf("%d\n", ret);
return 0;
}
10、软件补丁
网络流貌似很不可做啊。比较明显的是可以状压跑最短路。(事实上不必显式建图)
vector<pii> G[maxn];
int dis[maxn];
int n, m;
void dij(int s, int n){
priority_queue<pii, vector<pii>, greater<pii> > q;
for(int i = 0; i < n; ++i) dis[i] = inf;
dis[s] = 0, q.push({dis[s], s});
while(!q.empty()){
int u = q.top().second, d = q.top().first; q.pop();
for(int i = 0; i < sz(G[u]); ++i){
int v = G[u][i].second, w = G[u][i].first;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
q.push({dis[v], v});
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
int lim = 1 << n, msk = lim - 1;
while(m--){
int w; char s1[25], s2[25]; scanf("%d%s%s", &w, s1, s2);
int b1 = 0, b2 = 0, f1 = 0, f2 = 0;
for(int i = 0; s1[i]; ++i){
if(s1[i] == '+') b1 |= 1 << i;
else if(s1[i] == '-') b2 |= 1 << i;
if(s2[i] == '-') f1 |= 1 << i;
else if(s2[i] == '+') f2 |= 1 << i;
}
f1 = ~f1 & msk;
for(int i = 0; i < lim; ++i){
if((i & b1) == b1 && ((~i & msk) & b2) == b2){
G[i].pb({w, i & f1 | f2});
}
}
}
dij(lim - 1, lim);
printf("%d\n", dis[0] != inf ? dis[0] : 0);
return 0;
}
11、数字梯形
有三问,区别在于是否有限制点、边只能经过一次。对于边经过一次,可以直接连边容量为 1 1 1;对于点,拆点 u u u 和 u ′ u' u′,之间连接容量为 1 1 1 的边,则点只能经过一次。最后跑最大费用最大流。
Dinic<int> dn;
int a[maxn][maxn], id[maxn][maxn];
int m, n, tot;
int solve(int vcap, int ecap){
int sp = tot * 2 + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= m; ++i) dn.add(sp, id[1][i], 1, 0);
for(int i = 1; i < m + n; ++i) dn.add(tot + id[n][i], tp, inf, 0);
for(int i = 1; i <= n; ++i){
for(int j = 1; j < m + i; ++j){
if(i < n) dn.add(tot + id[i][j], id[i + 1][j], ecap, 0);
if(i < n) dn.add(tot + id[i][j], id[i + 1][j + 1], ecap, 0);
dn.add(id[i][j], tot + id[i][j], vcap, -a[i][j]);
}
}
return -dn.solve(sp, tp).second;
}
int main() {
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; ++i){
for(int j = 1; j < m + i; ++j){
scanf("%d", &a[i][j]);
id[i][j] = ++tot;
}
}
printf("%d\n", solve(1, 1));
printf("%d\n", solve(inf, 1));
printf("%d\n", solve(inf, inf));
return 0;
}
12、运输问题
S S S 向所有仓库连边 ( a i , 0 ) (a_i, 0) (ai,0),所有商店向 T T T 连边 ( b i , 0 ) (b_i, 0) (bi,0),仓库和商店之间连边 ( i n f , c i j ) (inf, c_{ij}) (inf,cij),分别求最小费用最大流和最大费用最大流。
Dinic<int> dn;
int a[maxn], b[maxn], c[maxn][maxn];
int m, n;
int solve(int f){
int sp = m + n + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= m; ++i) dn.add(sp, i, a[i], 0);
for(int i = 1; i <= n; ++i) dn.add(m + i, tp, b[i], 0);
for(int i = 1; i <= m; ++i){
for(int j = 1; j <= n; ++j){
dn.add(i, m + j, inf, f * c[i][j]);
}
}
return f * dn.solve(sp, tp).second;
}
int main() {
scanf("%d%d", &m, &n);
for(int i = 1; i <= m; ++i) scanf("%d", &a[i]);
for(int i = 1; i <= n; ++i) scanf("%d", &b[i]);
for(int i = 1; i <= m; ++i)
for(int j = 1; j <= n; ++j) scanf("%d", &c[i][j]);
printf("%d\n", solve(1));
printf("%d\n", solve(-1));
return 0;
}
13、分配问题
二分图最佳匹配,除了 k m km km 算法以外,网络流做法同上题,将边容量改成 1 1 1 即可。
Dinic<int> dn;
int c[maxn][maxn];
int n;
int solve(int f){
int sp = n * 2 + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= n; ++i) dn.add(sp, i, 1, 0);
for(int i = 1; i <= n; ++i) dn.add(n + i, tp, 1, 0);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
dn.add(i, n + j, inf, f * c[i][j]);
}
}
return f * dn.solve(sp, tp).second;
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j) scanf("%d", &c[i][j]);
printf("%d\n", solve(1));
printf("%d\n", solve(-1));
return 0;
}
14、负载平衡
除了贪心做法外,网络流做法如下。
显然可以得到最终每个仓库的库存数,每个仓库拆点
u
u
u 和
u
′
u'
u′,
S
S
S 向所有需要减少库存的仓库
u
u
u 连边,容量为需要减少的库存数,所有需要增加库存的
u
′
u'
u′ 向
T
T
T 连边,容量为需要增加的库存数。对所有
u
i
u_i
ui,向
u
i
−
1
u_{i-1}
ui−1 和
u
i
+
1
u_{i+1}
ui+1 、
u
i
−
1
′
u'_{i-1}
ui−1′ 和
u
i
+
1
′
u'_{i+1}
ui+1′ 连接
(
i
n
f
,
1
)
(inf, 1)
(inf,1) 的边,则最终满流则代表负载平衡,最小费用则为答案。
Dinic<int> dn;
int a[maxn];
int n;
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
int sp = n * 2 + 1, tp = sp + 1;
dn.init(tp);
int sum = 0;
for(int i = 1; i <= n; ++i) sum += a[i];
sum /= n;
for(int i = 1; i <= n; ++i){
if(a[i] > sum) dn.add(sp, i, a[i] - sum, 0);
else if(a[i] < sum) dn.add(n + i, tp, sum - a[i], 0);
int l = i == 1 ? n : i - 1, r = i == n ? 1 : i + 1;
dn.add(i, l, inf, 1), dn.add(i, n + l, inf, 1);
dn.add(i, r, inf, 1), dn.add(i, n + r, inf, 1);
}
int ret = dn.solve(sp, tp).second;
printf("%d\n", ret);
return 0;
}
15、最长 k 可重区间集
离散化,对于区间 ( l i , r i ) (l_i,~r_i) (li, ri), l i l_i li 向 r i r_i ri 连 ( 1 , − z i ) (1, -z_i) (1,−zi) 的边,按 x x x 轴正方向,每个点向下一点连 ( i n f , 0 ) (inf, 0) (inf,0) 的边, S S S 向最左端的点连 ( k , 0 ) (k, 0) (k,0) 的边,最右端的点向 T T T 连 ( k , 0 ) (k, 0) (k,0) 的边。对于单次增广,数轴上每个点只会被经过一次,求最小费用最大流即为 k k k 可重。
Dinic<int> dn;
pii a[maxn];
int xi[maxn], wi[maxn];
int n, k, tot;
int main() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i){
scanf("%d%d", &a[i].first, &a[i].second);
if(a[i].first > a[i].second) swap(a[i].first, a[i].second);
wi[i] = a[i].second - a[i].first;
xi[++tot] = a[i].first, xi[++tot] = a[i].second;
}
sort(xi + 1, xi + 1 + tot);
tot = unique(xi + 1, xi + 1 + tot) - xi - 1;
for(int i = 1; i <= n; ++i){
a[i].first = lower_bound(xi + 1, xi + 1 + tot, a[i].first) - xi;
a[i].second = lower_bound(xi + 1, xi + 1 + tot, a[i].second) - xi;
}
int sp = tot + 1, tp = sp + 1;
dn.init(tp);
dn.add(sp, 1, k, 0), dn.add(tot, tp, k, 0);
for(int i = 1; i <= n; ++i) dn.add(a[i].first, a[i].second, 1, -wi[i]);
for(int i = 1; i < tot; ++i) dn.add(i, i + 1, inf, 0);
int ret = -dn.solve(sp, tp).second;
printf("%d\n", ret);
return 0;
}
16、星际转移
按时间分层,有两种做法。一种时按时间每次动态加边跑最大流,另一种是直接二分答案建图跑最大流,个人更倾向于后者。二分答案 t i m tim tim, 0 0 0 时刻的地球作为 S S S, t i m tim tim 时刻的月球作为 T T T,按时间分层建图,对于某个空间站 u u u, u t i m − 1 u_{tim-1} utim−1 向 u t i m u_{tim} utim 连容量为 i n f inf inf 的边,表示人可以在某一个空间站停留。对于某一航线相邻的空间站 u u u -> v v v, u t i m − 1 u_{tim-1} utim−1 向 v t i m v_{tim} vtim 连容量为太空船容量的边。最后跑最大流判断。
Dinic<int> dn;
vector<int> G[maxn];
int num[maxn];
int n, m, k;
int judge(int tim){
int sp = 2, tp = tim * n + 1;
dn.init(tim * n + n);
for(int i = 1; i <= m; ++i){
for(int j = 1; j <= tim; ++j){
int now = G[i][j % sz(G[i])], pre = G[i][(j - 1) % sz(G[i])];
dn.add((j - 1) * n + pre, j * n + now, num[i]);
}
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= tim; ++j){
dn.add((j - 1) * n + i, j * n + i, inf);
}
}
return dn.solve(sp, tp) >= k;
}
int main() {
scanf("%d%d%d", &n, &m, &k);
n += 2;
for(int i = 1; i <= m; ++i){
scanf("%d", &num[i]);
int x; scanf("%d", &x);
while(x--){
int tmp; scanf("%d", &tmp);
G[i].pb(tmp + 2);
}
}
int l = 1, r = 1000, mid, ret = 0;
while(l <= r){
mid = gmid;
if(judge(mid)) r = mid - 1, ret = mid;
else l = mid + 1;
}
printf("%d\n", ret);
return 0;
}
17、孤岛营救
黑人问号。状压 b f s bfs bfs。
const int xp[] = {0, 0, -1, 1};
const int yp[] = {-1, 1, 0, 0};
struct Node{
int x, y, sta, cnt;
};
vector<int> G[maxn][maxn];
int mp[10][10][10][10], dis[10][10][1 << 10];
int n, m, p, k, s;
int check(int x, int y){
return x >= 0 && x < n && y >= 0 && y < m;
}
int bfs(){
queue<Node> q;
int lim = 1 << p;
for(int i = 0; i < n; ++i)
for(int j = 0; j < m; ++j)
for(int k = 0; k < lim; ++k) dis[i][j][k] = inf;
int sta = 0;
for(auto x : G[0][0]) sta |= 1 << (x - 1);
dis[0][0][0] = 0, q.push({0, 0, sta, 0});
while(!q.empty()){
Node now = q.front(); q.pop();
if(now.x == n - 1 && now.y == m - 1) return now.cnt;
for(int i = 0; i < 4; ++i){
Node nxt = {now.x + xp[i], now.y + yp[i], now.sta, now.cnt + 1};
if(!check(nxt.x, nxt.y)) continue;
int g = mp[now.x][now.y][nxt.x][nxt.y];
if(g == -1 || g > 0 && ((1 << (g - 1)) & now.sta) == 0) continue;
for(auto x : G[nxt.x][nxt.y]) nxt.sta |= 1 << (x - 1);
if(dis[nxt.x][nxt.y][nxt.sta] > dis[now.x][now.y][now.sta] + 1){
dis[nxt.x][nxt.y][nxt.sta] = nxt.cnt;
q.push(nxt);
}
}
}
return -1;
}
int main() {
scanf("%d%d%d", &n, &m, &p);
scanf("%d", &k);
while(k--){
int x1, y1, x2, y2, g; scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &g);
--x1, --y1, --x2, --y2;
if(g == 0) mp[x1][y1][x2][y2] = mp[x2][y2][x1][y1] = -1;
else mp[x1][y1][x2][y2] = mp[x2][y2][x1][y1] = g;
}
scanf("%d", &s);
while(s--){
int x, y, q; scanf("%d%d%d", &x, &y, &q);
--x, --y;
G[x][y].pb(q);
}
int ret = bfs();
printf("%d\n", ret);
return 0;
}
18、航空路线
即求从 S S S 到 T T T 两条不相交的经过点最多的路径。拆点,除起点终点外容量为 1 1 1,起点终点容量为 2 2 2,对于每个点,费用设置为 − 1 -1 −1,再求最小费用最大流。
void print(int n, string s[]){
for(int u = sp; u != tp; ){
for(auto e : G[u]){
if(!G[e.v][e.rev].cap) continue;
--G[e.v][e.rev].cap;
u = e.v;
if(e.cot) cout << s[e.v - n] << endl;
break;
}
}
for(int u = tp; u != sp; ){
for(auto e : G[u]){
if(!e.cap) continue;
--e.cap;
u = e.v;
if(e.cot && e.v != n) cout << s[e.v] << endl;
break;
}
}
}
Dinic<int> dn;
map<string, int> mp;
string s[maxn];
int n, m;
int main() {
scanf("%d%d", &n, &m);
int sp = n * 2 + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= n; ++i){
cin >> s[i];
if(i == 1 || i == n) dn.add(i, n + i, 2, -1);
else dn.add(i, n + i, 1, -1);
mp[s[i]] = i;
}
dn.add(sp, 1, 2, 0);
dn.add(n + n, tp, 2, 0);
while(m--){
string u, v; cin >> u >> v;
dn.add(n + mp[u], mp[v], inf, 0);
}
int ret = -dn.solve(sp, tp).second;
if(!ret) { cout << "No Solution!" << endl; return 0; }
cout << ret - 2 << endl;
dn.print(n, s);
return 0;
}
19、汽车加油行驶
汽车只能走 k k k 步,分层图最短路,第 i i i 层图代表汽车还能走 k − i k-i k−i 步,按照题意建图跑最短路即可。狂 w a wa wa 一个地方是新设加油站需费用 C C C (不包含加油费 A A A),不是说无需付加油费。
inline int getId(int x, int y, int t){
return t * n * n + (x - 1) * n + y;
}
int main() {
scanf("%d%d%d%d%d", &n, &k, &a, &b, &c);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
scanf("%d", &mp[i][j]);
}
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
for(int o = 1; o <= k; ++o){
int id = getId(i, j, mp[i][j] ? 0 : o);
int w = mp[i][j] ? a : 0;
if(i > 1) G[getId(i - 1, j, o - 1)].pb({w, id});
if(j > 1) G[getId(i, j - 1, o - 1)].pb({w, id});
if(i < n) G[getId(i + 1, j, o - 1)].pb({b + w, id});
if(j < n) G[getId(i, j + 1, o - 1)].pb({b + w, id});
G[getId(i, j, o)].pb({c + a, getId(i, j, 0)});
}
}
}
int t = n * n * (k + 1) + 1;
for(int i = 0; i <= k; ++i) G[getId(n, n, i)].pb({0, t});
int ret = dij(1, t);
printf("%d\n", ret);
return 0;
}
20、深海机器人
直接在网格图上连边,求最小费用最大流即可。
Dinic<int> dn;
int n, m, a, b;
inline int getId(int x, int y){
return (x - 1) * m + y;
}
int main() {
scanf("%d%d%d%d", &a, &b, &n, &m);
++n, ++m;
int sp = n * m + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= n; ++i){
for(int j = 1; j < m; ++j){
int tmp; scanf("%d", &tmp);
dn.add(getId(i, j), getId(i, j + 1), 1, -tmp);
dn.add(getId(i, j), getId(i, j + 1), inf, 0);
}
}
for(int j = 1; j <= m; ++j){
for(int i = 1; i < n; ++i){
int tmp; scanf("%d", &tmp);
dn.add(getId(i, j), getId(i + 1, j), 1, -tmp);
dn.add(getId(i, j), getId(i + 1, j), inf, 0);
}
}
while(a--){
int k, x, y; scanf("%d%d%d", &k, &x, &y); ++x, ++y;
dn.add(sp, getId(x, y), k, 0);
}
while(b--){
int r, x, y; scanf("%d%d%d", &r, &x, &y); ++x, ++y;
dn.add(getId(x, y), tp, r, 0);
}
int ret = -dn.solve(sp, tp).second;
printf("%d\n", ret);
return 0;
}
21、火星探险
拆点,岩石点内部先连 ( 1 , − 1 ) (1, -1) (1,−1) 的边,除障碍外连 ( i n f , 0 ) (inf, 0) (inf,0) 的边,每个点向右、向下连 ( i n f , 0 ) (inf, 0) (inf,0) 的点, S S S 向左上角点连 ( k , 0 ) (k, 0) (k,0) 的边,右下角点向 T T T 连 ( k , 0 ) (k, 0) (k,0) 的边,求最小费用最大流。
void print(int n, int m, int ret){
int tot = n * m;
for(int i = 1; i <= ret; ++i){
int u = tot + 1, aim = tot * 2;
while(u != aim){
for(auto e : G[u]){
if(!G[e.v][e.rev].cap || e.v == u - tot || e.v > u) continue;
--G[e.v][e.rev].cap;
printf("%d %d\n", i, (u - tot - 1) / m == (e.v - 1) / m);
u = e.v + tot;
break;
}
}
}
}
Dinic<int> dn;
int a[105][105], id[105][105];
int n, m, k;
int main() {
scanf("%d%d%d", &k, &m, &n);
int tot = 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
scanf("%d", &a[i][j]);
id[i][j] = ++tot;
}
}
int sp = tot * 2 + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
if(a[i][j] == 2) dn.add(id[i][j], tot + id[i][j], 1, -1);
if(a[i][j] != 1) dn.add(id[i][j], tot + id[i][j], k, 0);
if(i < n) dn.add(tot + id[i][j], id[i + 1][j], k, 0);
if(j < m) dn.add(tot + id[i][j], id[i][j + 1], k, 0);
}
}
dn.add(sp, id[1][1], k, 0);
dn.add(tot + id[n][m], tp, k, 0);
int ret = dn.solve(sp, tp).first;
dn.print(n, m, ret);
return 0;
}
22、骑士共存
将棋盘黑白染色,左侧黑点,右侧白点,冲突点之间连容量为 i n f inf inf 的边, S S S 向所有黑点连容量为 1 1 1 的边,所有白点向 T T T 连容量为 1 1 1 的边,求最小割即可得答案。
const int xp[] = {-2, -2, -1, -1, 1, 1, 2, 2};
const int yp[] = {-1, 1, -2, 2, -2, 2, -1, 1};
Dinic<int> dn;
int a[205][205];
int n, m;
int check(int x, int y){
return x >= 1 && x <= n && y >= 1 && y <= n;
}
int getId(int x, int y){
return (x - 1) * n + y;
}
int getCol(int x, int y){
return (x & 1) && (y & 1) || (~x & 1) && (~y & 1);
}
int main() {
scanf("%d%d", &n, &m);
int ret = n * n - m;
while(m--){
int x, y; scanf("%d%d", &x, &y);
a[x][y] = 1;
}
int sp = n * n + 1, tp = sp + 1;
dn.init(tp);
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
if(a[i][j]) continue;
int id = getId(i, j), c = getCol(i, j);
if(c){
dn.add(id, tp, 1);
continue;
}
dn.add(sp, id, 1);
for(int k = 0; k < 8; ++k){
int x = i + xp[k], y = j + yp[k];
if(!check(x, y) || a[x][y] || getCol(x, y) == c) continue;
dn.add(id, getId(x, y), inf);
}
}
}
ret -= dn.solve(sp, tp);
printf("%d\n", ret);
return 0;
}
23、最长 k 可重线段集
基本做法同最长 k k k 可重区间集,只需要有 x x x 坐标即可,然后权值再改改,但有特殊情况。当线段垂直于 x x x 轴时,对于区间为 [ x i 1 , x i 1 ] [x_{i1},x_{i1}] [xi1,xi1],而其他线段对应区间 ( x i 1 , x i 2 ) (x_{i1},x_{i2}) (xi1,xi2),区间有开有闭,难以处理,并且当 x i 1 x_{i1} xi1 和 x i 2 x_{i2} xi2 相等时直接连边会有自环。将每个点拆分成两个,分别代表端点闭合或者不闭合,每个区间都表示为左闭右开,具体为 [ x , x ] [x,x] [x,x] -> [ 2 x , 2 x + 1 ] [2x,2x+1] [2x,2x+1], ( x 1 , x 2 ) (x_1,x_2) (x1,x2) -> [ 2 x 1 + 1 , 2 x 2 ) [2x_1+1,2x_2) [2x1+1,2x2)。
Dinic<int> dn;
pii a[maxn];
int xi[maxn], wi[maxn];
int n, k, tot;
#define pw(x) ((x)*(x))
int main() {
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i){
int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
wi[i] = sqrt(pw((ll)x1 - x2) + pw((ll)y1 - y2));
x1 <<= 1, x2 <<= 1;
if(x1 == x2) ++x2;
else ++x1;
a[i].first = x1, a[i].second = x2;
xi[++tot] = x1, xi[++tot] = x2;
}
sort(xi + 1, xi + 1 + tot);
tot = unique(xi + 1, xi + 1 + tot) - xi - 1;
for(int i = 1; i <= n; ++i){
a[i].first = lower_bound(xi + 1, xi + 1 + tot, a[i].first) - xi;
a[i].second = lower_bound(xi + 1, xi + 1 + tot, a[i].second) - xi;
}
int sp = tot + 1, tp = sp + 1;
dn.init(tp);
dn.add(sp, 1, k, 0), dn.add(tot, tp, k, 0);
for(int i = 1; i <= n; ++i) dn.add(a[i].first, a[i].second, 1, -wi[i]);
for(int i = 1; i < tot; ++i) dn.add(i, i + 1, inf, 0);
int ret = -dn.solve(sp, tp).second;
printf("%d\n", ret);
return 0;
}
T
i
p
s
Tips
Tips:以上题目均仅在
L
i
b
r
e
O
J
LibreOJ
LibreOJ 上提交通过。
(话说怎么只有 23 23 23 道题)