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、太空飞行计划
求最大收益即为求最小花费,初始先加上所有收入,再求最小花费。根据冲突关系建边,每个实验 向所需仪器编号连接容量为 的边,源点 向 连接容量为 的边,每种仪器 向汇点 连接容量为 的边,则要么进行实验 ,割去对应仪器集合的所有连向 的边(即花费),要么割去实验 代表边。求最小割即为最小花费。
至于方案输出,再从源点 做一次 ,则某条边为割边当且仅当两端点一端可达且另一端不可达。
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、最小路径覆盖
有向图最小路径覆盖,将每个点 拆成 和 ,对边 ,连接 和 ,则最小路径覆盖数 点数 最大匹配。求最大流, 输出方案。
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、魔术球
这题有贪心的解法,这里说一下个人的做法。
先预处理出所有数字的后继。二分答案
,问题转化为判定能否用
条链覆盖
个球。对每个球
拆点
和
,
向
连接容量为
、费用为
的边,
向所有球连容量
、费用
的边,所有球向后继连边容量
、费用
的边,所有球向
连容量
、费用
的边,求最小费用最大流判定。
: 这题其实是最小路径覆盖,无需费用流,只需要判定是否最小路径覆盖数 <= ,并且也可以不二分,枚举答案每次在残留网络上跑就行。
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、圆桌聚餐
向第 个单位连容量为 的边,第 张餐桌向 连容量为 的边,所有单位分别向每张餐桌连容量为 的边,求最大流并输出方案。
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、最长递增子序列
求出以 结尾的最长递增子序列,特判 。 向 的点连边, 的向 连边,对于 ,向后继状态 连边,以上边容量皆为 。求最大流则为第二个询问的答案。
对于第三个询问, 向 连容量为 的边,若 , 向 连容量为 的边,再求一次最大流。
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、试题库
向所有题目连容量为 的边,所有题目类型向 连容量为所需题数的边,对于每道题目,向可以归类的类型连容量为 的边。求最大流并输出方案。
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、餐巾计划
考虑到每条餐巾有新、旧两种状态,对每条餐巾建立两个点 和 ,分别代表旧餐巾和新餐巾。 向所有 连边 表示第 天可选择以单位价格为 购入新餐巾, 向 连边 ,表示第 天需要 条新餐巾, 向 连 的边,表示第 天产生 条旧餐巾, 向 连边 表示旧餐巾可以留到下一天, 向 连边 表示快洗 天后旧餐巾变新餐巾,慢洗同理。最后求最小费用流即可。
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、数字梯形
有三问,区别在于是否有限制点、边只能经过一次。对于边经过一次,可以直接连边容量为 ;对于点,拆点 和 ,之间连接容量为 的边,则点只能经过一次。最后跑最大费用最大流。
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、运输问题
向所有仓库连边 ,所有商店向 连边 ,仓库和商店之间连边 ,分别求最小费用最大流和最大费用最大流。
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、分配问题
二分图最佳匹配,除了 算法以外,网络流做法同上题,将边容量改成 即可。
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、负载平衡
除了贪心做法外,网络流做法如下。
显然可以得到最终每个仓库的库存数,每个仓库拆点
和
,
向所有需要减少库存的仓库
连边,容量为需要减少的库存数,所有需要增加库存的
向
连边,容量为需要增加的库存数。对所有
,向
和
、
和
连接
的边,则最终满流则代表负载平衡,最小费用则为答案。
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 可重区间集
离散化,对于区间 , 向 连 的边,按 轴正方向,每个点向下一点连 的边, 向最左端的点连 的边,最右端的点向 连 的边。对于单次增广,数轴上每个点只会被经过一次,求最小费用最大流即为 可重。
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、星际转移
按时间分层,有两种做法。一种时按时间每次动态加边跑最大流,另一种是直接二分答案建图跑最大流,个人更倾向于后者。二分答案 , 时刻的地球作为 , 时刻的月球作为 ,按时间分层建图,对于某个空间站 , 向 连容量为 的边,表示人可以在某一个空间站停留。对于某一航线相邻的空间站 -> , 向 连容量为太空船容量的边。最后跑最大流判断。
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、孤岛营救
黑人问号。状压 。
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、航空路线
即求从 到 两条不相交的经过点最多的路径。拆点,除起点终点外容量为 ,起点终点容量为 ,对于每个点,费用设置为 ,再求最小费用最大流。
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、汽车加油行驶
汽车只能走 步,分层图最短路,第 层图代表汽车还能走 步,按照题意建图跑最短路即可。狂 一个地方是新设加油站需费用 (不包含加油费 ),不是说无需付加油费。
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、火星探险
拆点,岩石点内部先连 的边,除障碍外连 的边,每个点向右、向下连 的点, 向左上角点连 的边,右下角点向 连 的边,求最小费用最大流。
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、骑士共存
将棋盘黑白染色,左侧黑点,右侧白点,冲突点之间连容量为 的边, 向所有黑点连容量为 的边,所有白点向 连容量为 的边,求最小割即可得答案。
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 可重线段集
基本做法同最长 可重区间集,只需要有 坐标即可,然后权值再改改,但有特殊情况。当线段垂直于 轴时,对于区间为 ,而其他线段对应区间 ,区间有开有闭,难以处理,并且当 和 相等时直接连边会有自环。将每个点拆分成两个,分别代表端点闭合或者不闭合,每个区间都表示为左闭右开,具体为 -> , -> 。
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;
}
:以上题目均仅在
上提交通过。
(话说怎么只有 道题)