周一
今天上午考完了,开心
暑假开启,全力以赴
最近主要是补题
poj 3061(尺取法)
学一学尺取法,挺简单的,就是两个指针
我可能无意中用过,不知道这叫做尺取法
满足某种单调性,用两个指针 O(n)的算法
#include <cstdio>
#include <algorithm>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e5 + 10;
int a[N], n, S;int main()
{int T; scanf("%d", &T);while(T--){scanf("%d%d", &n, &S);_for(i, 1, n) scanf("%d", &a[i]);int sum = 0, l = 1, r = 0, ans = 1e9;while(l <= n){while(sum < S && r < n) sum += a[++r];if(sum < S) break;ans = min(ans, r - l + 1);sum -= a[l++];}printf("%d\n", ans == 1e9 ? 0 : ans);}return 0;
}
Alice and Bob (尺取法+二分查找)
这道题的关键是求一个数组f[i]
表示以i为左端点,最小的右端点使得不同的数有k个。f[i]用尺取法求
这时,对于一个查询,枚举每一个左端点,若f[i] > R则没有贡献
若f[i] <= R 则贡献为R-f[i]+1
注意f[i]是递增的,所以我们可以二分出第一个大于R的f[i]
从这个f[i]以后的都没有贡献
这个f[i]之前的贡献是(R - f[i] + 1) 预处理f[i]前缀和可以O(1)求
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e5 + 10;
int f[N], a[N], cnt, n, m, k;
unordered_map<int, int> mp;
ll s[N];int main()
{scanf("%d%d%d", &n, &m, &k);_for(i, 1, n) scanf("%d", &a[i]);int l = 1, r = 0;while(l <= n){while(cnt < k && r < n){r++;if(++mp[a[r]] == 1) cnt++;}if(cnt < k){_for(i, l, n) f[i] = n + 1;break;}f[l] = r;if(--mp[a[l]] == 0) cnt--;l++;}_for(i, 1, n) s[i] = s[i - 1] + f[i];ll last = 0;while(m--){int l, r, L, R;scanf("%d%d", &l, &r);L = min(l ^ last, r ^ last) + 1;R = max(l ^ last, r ^ last) + 1;int pos = upper_bound(f + L, f + R + 1, R) - f - 1;printf("%lld\n", last = 1LL * (pos - L + 1) * (R + 1) - (s[pos] - s[L - 1]));}return 0;
}
P3376 【模板】网络最大流 (Dinic)
最近几天学一波网络流,暑假的第一个专题
大致的思路就是不断的找增广路,直到找不到增广路的时候就是最大流
其中添加反向边提供反悔机制。其实有点像匈牙利算法
其中要加一些优化
1.bfs分层,使得在dfs的时候只能从这一层到下一层,从而不会dfs时候往回找等等。这样不会影响正确性
2.同时多路增广
3.用cur数组,下一次遍历到这个点的时候直接跳过一些点
具体看注释吧
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;typedef long long ll; //此模板的最大流开long long
const int N = 210; //总点数
struct Edge { int from, to, flow; }; //flow表示这条边的容量 若容量为1,流过1之后那么容量减1变为0 如二分匹配输出方案,flow=0的边就是匹配边
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N]; //d数组表示bfs分层的深度
int n, m, s, t; //s t是源点和汇点 cur是一个优化表示当前点到哪一个节点了,减少了很多遍历void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs() //bfs标记深度优化 dfs时只能从这一层搜到下一层
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow) //记住考虑的是残量网络内的图 这条边还可以流才搜{d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t]; //如果h[t]为0说明不联通,此时已经是最大流,整个算法结束
}ll dfs(int u, ll in) //in表示当前节点流入的流量
{if(u == t) return in; //找到汇点就returnll out = 0; //out表示这个节点流出的流量for(int& i = cur[u]; i < g[u].size(); i++) //cur优化 注意这里引用{Edge& e = edge[g[u][i]]; //这里要引用,因为要修改if(d[u] + 1 == d[e.to] && e.flow) //dfs时有的流才流{ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f; //反向边容量增加out += f; in -= f;if(in == 0) break;}}return out;
}void build() //建模,修改输入输出,如何加边,数据范围
{scanf("%d%d%d%d", &n, &m, &s, &t);while(m--){int from, to, flow;scanf("%d%d%d", &from, &to, &flow);add(from, to, flow); //加边}
}int main()
{build();ll ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}printf("%lld\n", ans);return 0;
}
P3381 【模板】最小费用最大流
给每条边设立单位流量的费用,这条边的费用等于流量乘以单位流量的费用
在最大流的条件下,求最小费用是多少
这个时候把bfs改成spfa就好了,每次寻找费用最小的增广路
因为每次只找到一条,所以之后就把dfs多路增广改成一条路增广,不用写dfs,写个while就行
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 5e3 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], n, m, s, t; //f[i]表示点i流入的流量
ll f[N];//p数组用来回溯
void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9; //最后联通
}void build() //建模
{scanf("%d%d%d%d", &n, &m, &s, &t);while(m--){int from, to, flow, cost;scanf("%d%d%d%d", &from, &to, &flow, &cost);add(from, to, flow, cost);}
}int main()
{build();ll maxflow = 0, mincost = 0;while(spfa()) //bfs改成spfa 每次找一条花费最小的增广路增广路{ //之后不需要dfs 因为只有一条增广路maxflow += f[t]; //更新答案mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t]; //找到回溯后修改容量edge[p[u] ^ 1].flow += f[t];}}printf("%lld %lld\n", maxflow, mincost);return 0;
}
比赛时套模板就行
接下来刷一波网络流24题
这哥们已经整理好了
网络流24题
P2756 飞行员配对方案问题(最大流实现二分图最大匹配)
源点向左边连容量为1的边,右边向汇点连容量为1的边
中间的边也是容量为1
这样最大流就是最大匹配
输出方案的话,边流过了1,那么flow变为0
匈牙利算法的时间复杂度时O(nm) 用最大流写更快一些
有些题的二分匹配数据范围大只能用最大流写
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 210;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N];
int n, m, s, t;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}void build()
{scanf("%d%d", &m, &n);s = 0; t = n + 1;_for(i, 1, m) add(s, i, 1);_for(i, m + 1, n) add(i, t, 1);while(1){int u, v;scanf("%d%d", &u, &v);if(u == -1 && v == -1) break;add(u, v, 1);}
}int main()
{build();ll ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}printf("%lld\n", ans);for(auto e: edge)if(!e.flow && e.from < e.to && e.from != s && e.to != t)printf("%d %d\n", e.from, e.to);return 0;
}
P4016 负载平衡问题(建模 + 最小费用最大流)
看题解的,很骚,自己想想不出来。毕竟这类题目没做过
首先先算出当前点需要给别人多少或者需要别人给自己多少
如果一个点需要给别人,那么从源点连一条边到这个点,费用0,容量为多出来的部分
这样子就有源点的流量流到这,它就可以给别人
如果一个点需要别人给,那么从这个点到汇点连一条边,费用0,容量为需要给的部分
这样子就表示它需要别人给它流量,然后把这个流量输出到汇点
这样子建图,跑一遍最大流,源点出去的流量满了,就说明已经平衡了。
也就是满足了最小费用最大流的最大流
接下来是最小费用,这时就相邻的点(注意两个方向)连一条边,容量无穷,费用为1
容量无穷是因为源点出来的边一定要饱和,费用为1即搬运的费用
这样子最小费用就是答案
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 100 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], a[N], n, m, s, t;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build()
{scanf("%d", &n);s = 0; t = n + 1;int sum = 0;_for(i, 1, n){scanf("%d", &a[i]);sum += a[i];}_for(i, 1, n){a[i] -= sum / n;if(a[i] > 0) add(s, i, a[i], 0);else if(a[i] < 0) add(i, t, -a[i], 0);if(i == n) add(i, 1, 1e9, 1);else add(i, i + 1, 1e9, 1);if(i == 1) add(i, n, 1e9, 1);else add(i, i - 1, 1e9, 1);}
}int main()
{build();ll maxflow = 0, mincost = 0;while(spfa()){maxflow += f[t];mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}printf("%lld\n", mincost);return 0;
}
P2761 软件补丁问题(状态压缩 + 最短路)
醉了,为什么把这个最短路的放到网络流里面……
我一直在往网络流的方向想,完全没思路
瞟了一眼题解说是最短路……
然后我很快就状压+最短路AC了
判断的时候我没用那些很骚的位运算,直接for循环
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1 << 21;
const int M = 100 + 10;
struct Edge { int w; string s1, s2; };
struct node
{int v, w;bool operator < (const node& rhs) const{return w > rhs.w;}
};
int d[N], n, m, s, t;
Edge e[M];bool check(string t, int u)
{int len = t.size();rep(i, 0, len){int now = (u & (1 << (len - i - 1)));if(t[i] == '+' && now) return false;if(t[i] == '-' && !now) return false;}return true;
}int deal(string t, int u)
{int len = t.size();rep(i, 0, len){if(t[i] == '-') u |= (1 << (len - i - 1));if(t[i] == '+') u &= ~(1 << (len - i - 1));}return u;
}int dijkstra()
{s = 0; t = (1 << n) - 1;memset(d, 0x3f, sizeof d);d[s] = 0;priority_queue<node> q;q.push(node{s, d[s]});while(!q.empty()){node x = q.top(); q.pop();int u = x.v;if(d[u] != x.w) continue;_for(i, 1, m)if(check(e[i].s1, u)){int v = deal(e[i].s2, u);if(d[v] > d[u] + e[i].w){d[v] = d[u] + e[i].w;q.push(node{v, d[v]});}}}return d[t] < 1e9 ? d[t] : 0;
}int main()
{scanf("%d%d", &n, &m);_for(i, 1, m) cin >> e[i].w >> e[i].s1 >> e[i].s2;printf("%d\n", dijkstra());return 0;
}
周二
继续刷网络流24题
P4011 孤岛营救问题 做过,是bfs+状压
怎么又把这个不是网络流的题放到网络流24题……
P2765 魔术球问题(有向图最小路径覆盖)
这题可以用匈牙利做,也可以用网络流做
先讲匈牙利
首先要有一个转化,这道题是一个隐式图
把每一个球看作点,如果可以相邻,也就是和为完全平方数,那么连一条边,如从小的到大的连一条边。这样就形成了一个有向无环图
这样子我们可以发现,一个柱子就是一条路径。每一个点仅在一条路径上
这就转化成了有向图的最小路径覆盖问题
这个问题之前刷kuangbin的匹配题单的时候遇到过,但是结论我有点遗忘了,是两个月以前了
结论是最小路径覆盖=节点数-最大匹配
具体来说,每一条有向边,就在二分图中建边
然后答案就是n - 最大匹配 注意这里的是n不是2*n
这道题限制的柱子其实就是最大的路径数
所以我们可以不断地加入点,然后求最大匹配,进而求出最小路径覆盖,一直到这个最小路径覆盖大于柱子数就退出
有几个地方要注意
1.可以不用每次加入点就全部重来求一遍最大匹配。可以在原来的基础上,直接从新加入的点开始找增广路。但是注意这时开始建图的时候加入边是从编号大的点往编号小的点连边,因为加入一个新点,实际上就要遍历所有因为这个新点而新加入的边,这时加入边的时候要从这个点往其他原来的点连边,也就是编号大的连到编号小的
当然每次重新跑一遍也可以过这道题
2.方案的输出问题。我开始时候另外开的数组来记录,后来发现可以写的更简单
巧用link数组,link[i],同时输出过的标记一下就行了,看代码
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e5 + 10;
int vis[N], link[N], n, m, ans;
vector<int> g[N];bool dfs(int u)
{for(int v: g[u]){if(vis[v]) continue;vis[v] = 1;if(!link[v] || dfs(link[v])){link[v] = u;return true;}}return false;
}int main()
{scanf("%d", &m);while(1){n++;_for(i, 1, n - 1){int t = n + i;if(fabs((int)sqrt(t) - sqrt(t)) < 1e-8)g[n].push_back(i); //注意这里的方向}_for(j, 1, n) vis[j] = 0;if(dfs(n)) ans++; //每次取匹配新加入的点即可if(n - ans > m) break;}printf("%d\n", --n);memset(vis, 0, sizeof vis);_for(i, 1, n)if(!vis[i]) //巧用vis数组和link数组输出方案{for(int now = i; now; now = link[now]){printf("%d ", now);vis[now] = 1;}puts("");}return 0;
}
最大流的写法差不多。代码量大一些
注意最大流要拆点,i*2是左边的点 i*2+1是右边的点,这样子变成二分图
匈牙利可以不用拆,因为这不影响这个算法的执行。但是最大流是要拆的
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e4 + 10;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N], vis[N], Next[N];
int ans, n, m, s, t;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}int main()
{scanf("%d", &m);s = 0; t = 1e4;while(1){n++;add(s, l(n), 1);add(r(n), t, 1);_for(i, 1, n - 1){int k = n + i;if(fabs((int)sqrt(k) - sqrt(k)) < 1e-8)add(l(i), r(n), 1);}while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}if(n - ans > m) break;}printf("%d\n", --n);for(auto e: edge){if(!e.flow && e.from != s && e.to != t && e.from < e.to)Next[e.from / 2] = e.to / 2;}_for(i, 1, n)if(!vis[i]){for(int now = i; now; now = Next[now]){printf("%d ", now);vis[now] = 1;}puts("");}return 0;
}
汽车加油行驶问题(分层图建图+最短路)
这题又做过,之前是用分层图+最短路AC的
也可以用费用流做,其实没必要
最短路问题可以用费用流来解决,但显然时间复杂度高很多,实际情况肯定用最短路
方法是设置最大流为1
从源点连到起点,费用0容量1,图中的边,边权为费用,容量为1
终点连到汇点,费用0容量1
这样跑一遍费用流就是最短路了
P3254 圆桌问题(二分图匹配)
有点像二分图多重匹配,有点遗忘了……
左边单位,右边圆周
源点连左边,容量为人数,右边连汇点,容量为限制
中间连容量为1的边
跑最大流,最大流等于单位总人数就ok
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;typedef long long ll;
const int N = 500;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N], ve[N];
int d[N], cur[N];
int n, m, s, t, sum;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}void build()
{scanf("%d%d", &m, &n);s = 0; t = n + m + 1;_for(i, 1, m){int x; scanf("%d", &x);add(s, i, x);sum += x;}_for(i, 1, n){int x; scanf("%d", &x);add(m + i, t, x);}_for(i, 1, m)_for(j, 1, n)add(i, m + j, 1);
}int main()
{build();int ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}if(ans != sum){puts("0");return 0;}puts("1");for(auto e: edge)if(!e.flow)ve[e.from].push_back(e.to - m);_for(i, 1, m){for(int x: ve[i]) printf("%d ", x);puts("");}return 0;
}
P2763 试题库问题(最大流实现二分图匹配)
这道题题目没讲清楚,一个题目只能匹配一种类型……
那么就和上一道非常类似了
注意输出方案的时候不能是方向边同时不是源点汇点
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;typedef long long ll;
const int N = 1e3 + 100;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N], ve[N];
int d[N], cur[N];
int n, m, k, s, t;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}void build()
{scanf("%d%d", &k, &n);s = 0; t = n + k + 1;_for(i, 1, k){int x; scanf("%d", &x);add(s, i, x);m += x;}_for(i, 1, n){add(k + i, t, 1);int x, y; scanf("%d", &x);while(x--){scanf("%d", &y);add(y, k + i, 1);}}
}int main()
{build();int ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}if(ans != m){puts("No Solution!");return 0;}for(auto e: edge)if(!e.flow && e.from < e.to && e.from != s && e.to != t) //注意这里 防止方向边 同时不是源点汇点ve[e.from].push_back(e.to - k);_for(i, 1, k){printf("%d:", i);for(int x: ve[i]) printf(" %d", x);puts("");}return 0;
}
P2764 最小路径覆盖问题
这题应该先做,再做那个平方的……
秒了
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;typedef long long ll;
const int N = 300 + 10;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N], ve[N];
int d[N], cur[N], Next[N], vis[N];
int n, m, k, s, t;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}void build()
{scanf("%d%d", &n, &m);s = 0; t = r(n) + 1;_for(i, 1, n) add(s, l(i), 1), add(r(i), t, 1);while(m--){int u, v;scanf("%d%d", &u, &v);add(l(u), r(v), 1);}
}int main()
{build();int ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}for(auto e: edge)if(!e.flow && e.from < e.to && e.from != s && e.to != t)Next[e.from / 2] = e.to / 2;_for(i, 1, n)if(!vis[i]){for(int now = i ; now; now = Next[now]){printf("%d ", now);vis[now] = 1;}puts("");}printf("%d\n", n - ans);return 0;
}
又写了一个匈牙利的
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;const int N = 200;
int link[N], Next[N], vis[N], n, m;
vector<int> g[N];bool dfs(int u)
{for(int v: g[u]){if(vis[v]) continue;vis[v] = 1;if(!link[v] || dfs(link[v])){link[v] = u;Next[u] = v;return true;}}return false;
}int main()
{scanf("%d%d", &n, &m);while(m--){int u, v;scanf("%d%d", &u, &v);g[u].push_back(v);}int ans = 0;_for(i, 1, n){memset(vis, 0, sizeof vis);ans += dfs(i);}memset(vis, 0, sizeof vis);_for(i, 1, n)if(!vis[i]){for(int now = i; now; now = Next[now]){printf("%d ", now);vis[now] = 1;}puts("");}printf("%d\n", n - ans);return 0;
}
P4014 分配问题(二分图最优匹配)
原来网络流可以解决二分图最大匹配,多重匹配,最优匹配……
之前刷匹配问题的时候还一个一个去学新的算法…………
这道题就费用流解决二分图最优匹配
这是最优匹配的裸题了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 210;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], a[N][N], n, m, s, t;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build(int op)
{edge.clear(); //清数组只用清边。其他数组都是spfa里面的,而spfa做很多次,都再里面清了_for(i, s, t) g[i].clear();_for(i, 1, n){add(s, i, 1, 0);add(i + n, t, 1, 0);}_for(i, 1, n)_for(j, 1, n)add(i, j + n, 1, op * a[i][j]);
}int solve()
{int maxflow = 0, mincost = 0;while(spfa()){maxflow += f[t];mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}return mincost;
}int main()
{scanf("%d", &n);s = 0; t = n * 2 + 1;_for(i, 1, n)_for(j, 1, n)scanf("%d", &a[i][j]);build(1);printf("%d\n", solve());build(-1);printf("%d\n", -solve());return 0;
}
poj 1966(最小割=最大流)
学习一个结论,最小割=最大流
割就是边集合,去掉这些边可以使得源点和汇点不联通
边的容量之和最小的割就是最小割
结论是最小割,即边的容量之和的最大流
这道题是无向图中最少去掉多少割点使得不联通
首先确定源点和汇点
注意这时不能用超级源点和超级汇点。这时要枚举选两个点,一个源点一个汇点
如果选超级源点和超级汇点的话,可能源点和汇点联通的时候,图是不联通
然后有个技巧,就是点化边
一个点拆为一个入点和一个出点,入点向出点连一条容量为1的边
原图中已有的边,从一个点的出点连到另一个点的入点,容量为无穷。连两条
这样子最小割就是最少删除的点。
这里容量为无穷的意思是不把其作为割的边。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define in(k) (k << 1)
#define out(k) (k << 1 | 1)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;typedef long long ll;
const int N = 110;
struct Edge
{int from, to, flow;Edge(int a, int b, int c): from(a), to(b), flow(c) {}
};
vector<Edge> edge;
vector<int> g[N], ve[N];
int d[N], cur[N], a[N][N];
int n, m, k, s, t;void add(int from, int to, int flow)
{edge.push_back(Edge(from, to, flow));g[from].push_back(edge.size() - 1);edge.push_back(Edge(to, from, 0));g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();rep(i, 0, g[u].size()){Edge e = edge[g[u][i]];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}void build()
{edge.clear();_for(i, 1, n * 2) g[i].clear();_for(i, 1, n)if(i != s / 2 && i != t / 2)add(in(i), out(i), 1);_for(i, 1, n)_for(j, i + 1, n)if(a[i][j]){add(out(i), in(j), 1e9);add(out(j), in(i), 1e9);}
}int main()
{while(~scanf("%d%d", &n, &m)){memset(a, 0, sizeof a);while(m--){int u, v;scanf(" (%d,%d)", &u, &v); //这个输入前面要加空格u++; v++;a[u][v] = 1;}int ans = 1e9;_for(i, 1, n)_for(j, i + 1, n)if(!a[i][j]){s = out(i); t = in(j);build();int maxflow = 0;while(bfs()){memset(cur, 0, sizeof(cur));maxflow += dfs(s, 1e18);}ans = min(ans, maxflow);}printf("%d\n", ans == 1e9 ? n : ans);}return 0;
}
P2762 太空飞行计划问题(最大权闭合子图)
想了挺久,没啥思路。看题解发现是一个新知识点
这时最大权闭合子图的模板题
再有向图中,一个点选了,就必须选其后继节点,则这个图是闭合的
每个点有一个权值,那么选一个子图使得权值和最大
这就是最大权闭合子图问题
解决方法是,从源点向权值为正的前连边,容量为权值
权值为负的点向汇点连边,容量为权值
其中原图中存在的点连边,容量为无穷大
跑一遍最大流=最小割
正点权之和-最大流就是最大点权
这样子,实验和器材的边不会被割,如果割了源点到实验的边代表不选
割了器材到汇点的边表示选,求最小割
最后输出方案的话,看最后一次bfs中的分层的值
d数组有值的话说明被遍历到,说明边是存在的
最后留下来的图就是选择情况
因此判断d数组的值即可
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 110;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N], a[N][N];
int sum, n, m, s, t;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}void build()
{scanf("%d%d", &m, &n);s = 0; t = m + n + 1;_for(i, 1, m){int x; scanf("%d", &x);sum += x;add(s, i, x);char tools[10000];memset(tools, 0, sizeof tools);cin.getline(tools, 10000);int ulen = 0, tool;while (sscanf(tools + ulen, "%d", &tool)==1){add(i, tool + m, 1e9);a[i][tool] = 1;if(tool == 0) ulen++;else while(tool) tool /= 10, ulen++;ulen++;}}_for(i, 1, n){int x; scanf("%d", &x);add(i + m, t, x);}
}int main()
{build();int ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}_for(i, 1, m) if(d[i]) printf("%d ", i); puts("");_for(i, 1, n) if(d[i + m]) printf("%d ", i); puts("");printf("%d\n", sum - ans);return 0;
}
P4015 运输问题(费用流)
费用流裸题
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 210;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], n, m, s, t;
int a[N], b[N], c[N][N];
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build(int op)
{edge.clear();_for(i, s, t) g[i].clear();_for(i, 1, m) add(s, i, a[i], 0);_for(i, 1, n) add(i + m, t, b[i], 0);_for(i, 1, m)_for(j, 1, n)add(i, j + m, 1e9, c[i][j] * op);
}ll solve()
{ll mincost = 0;while(spfa()){mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}return mincost;
}int main()
{scanf("%d%d", &m, &n);s = 0; t = n + m + 1;_for(i, 1, m) scanf("%d", &a[i]);_for(i, 1, n) scanf("%d", &b[i]);_for(i, 1, m)_for(j, 1, n)scanf("%d", &c[i][j]);build(1);printf("%lld\n", solve());build(-1);printf("%lld\n", -solve());return 0;
}
周三
P2774 方格取数问题(二分图最大权独立集)
这题是看题解的,没什么思路。做的网络流的题还是太少
这题是二分图最大权独立集
首先这题的要求是选了一个点那么它相邻的点都不能选
我自己想的时候就在想怎么建图来实现这个功能,发现很难,想不出来
正解是逆向思维,假设全都选,看怎么删除使得不互斥,这样子就容易很多
由“删除”可以想到最小割,刚好删除的最小就是权值和最大
也就是说要构建一个图,使得删除一条边就代表不选这个点
这时要利用坐标之和的奇偶性来划成二分图,这是一个很巧妙的思路
相邻的点坐标之和奇偶性一定是不同的,所以可以把坐标之和为奇数的放左边,偶数的放右边,互斥的点连边
因为坐标之和奇偶性相同的不一定不互斥,所以这是一个二分图
然后源点向左边的点连边,容量为点的权值,右边的点向汇点连边,同样容量为点的权值
中间互斥的点连边,容量为无穷大,代表不割。
当实现最小割的时候,源点和汇点不联通,这时一定是没有互斥的点的
如果由互斥的点,那么它们都选,它们之间的边不可能割掉,这样源点汇点就联通了。
最后最小割=最大流。
很秀,还是要多学习建模套路
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e4 + 10;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N];
int n, m, s, t, sum;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}int id(int i, int j) { return (i - 1) * m + j; }void build()
{scanf("%d%d", &n, &m);s = 0; t = n * m + 1;_for(i, 1, n)_for(j, 1, m){int x; scanf("%d", &x);sum += x;if((i + j) % 2 == 1){add(s, id(i, j), x);if(j + 1 <= m) add(id(i, j), id(i, j + 1), 1e9);if(j - 1 >= 1) add(id(i, j), id(i, j - 1), 1e9);if(i + 1 <= n) add(id(i, j), id(i + 1, j), 1e9);if(i - 1 >= 1) add(id(i, j), id(i - 1, j), 1e9);}else add(id(i, j), t, x);}
}int main()
{build();int ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}printf("%d\n", sum - ans);return 0;
}
P2766 最长不下降子序列问题(最大流)
这题还是看题解的。
其实不难,关键是理解网络流的这个流的作用
对于这道题而言,一股流就是一个最长不下降子序列
要求有多少个最长不下降子序列,就是有多少股流
于是可以根据dp数组分层,来建模
因为一个点只能选一次,所以把一个点拆一下,中间连一个容量为1的边
如果dp[i] = 1那么源点向其连容量为1的边,如果dp[i] = ans 那么其向汇点连容量为1的边
容量为1表示整个子序列的流为1
第三问一个点可以用无限次,那么就把中间这条边的容量设置为无穷就好了。同时和源点和汇点相连的边的容量也设为无穷
最后注意特判一下n=1的情况
#include <bits/stdc++.h>
#define in(i) (i << 1)
#define out(i) (i << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e3 + 10;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], a[N], dp[N], cur[N];
int n, m, s, t, sum;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}int solve()
{int res = 0;while(bfs()){memset(cur, 0, sizeof(cur));res += dfs(s, 1e18);}return res;
}int main()
{scanf("%d", &n);_for(i, 1, n) scanf("%d", &a[i]);int ans = 0;_for(i, 1, n){dp[i] = 1;_for(j, 1, i - 1)if(a[j] <= a[i])dp[i] = max(dp[i], dp[j] + 1);ans = max(ans, dp[i]);}printf("%d\n", ans);s = 0; t = out(n) + 1;_for(i, 1, n){add(in(i), out(i), 1);if(dp[i] == 1) add(s, in(i), 1);if(dp[i] == ans) add(out(i), t, 1);_for(j, i + 1, n)if(a[i] <= a[j] && dp[i] + 1 == dp[j])add(out(i), in(j), 1);}printf("%d\n", solve());edge.clear();_for(i, s, t) g[i].clear();_for(i, 1, n){if(i == 1 || i == n) add(in(i), out(i), 1e9);else add(in(i), out(i), 1);if(dp[i] == 1){if(i == 1) add(s, in(i), 1e9);else add(s, in(i), 1);}if(dp[i] == ans){if(i == n) add(out(i), t, 1e9);else add(out(i), t, 1);}_for(j, i + 1, n)if(a[i] <= a[j] && dp[i] + 1 == dp[j])add(out(i), in(j), 1);}if(n == 1) puts("1"); //巨坑else printf("%d\n", solve());return 0;
}
P3358 最长k可重区间集问题(网络流的串并联)
依然是知识盲区……24题里很少有独立做出来的,有很多很骚的建图技巧
这题类比电路里面的串并联
首先相交不能超过k个区间,这就是限制,然后求最长区间长度。可以想到这个限制就是最大流,求得这个最值就是费用流。有限制条件下求最值,即最小费用最大流
关键是怎么构造这个限制
对于一个区间,怎么表示用这个区间呢。可以源点到l连一条边,容量为1费用0,r向汇点连一条边,容量为1费用0,l到r连一条边,容量为1费用为区间长度
这个应该蛮好理解的,这条流流过就代表选这个区间,然后费用为区间长度
关键是怎么构造这个相交的区间不超过k个
首先怎么把相交这件事情体现在图上。
可以从源点给k条流(写两个源点),然后相交的区间并联,这样最多就只能选k个区间
不相交的区间就串联,这样子一条流可以直接流过,代表没有限制
按照上面说的建图方法就已经是全部并联了,那么这时我们就再加上串联的边就好了
如果两个区间不相交,就把前一个区间的r向后一个区间的l连一条边,容量为1费用为0
注意要计算的是区间长度和,所以只有一个区间l向r连边的时候才有费用,其他的边都没有费用
这里的流量就是用来限制的
然后注意要离散化一下
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e3 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], lsh[N], l[N], r[N], cnt;
int n, k, m, s, s2, t;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}int id(int x) { return lower_bound(lsh + 1, lsh + m + 1, x) - lsh + 1; }void build()
{scanf("%d%d", &n, &k);s = 0; s2 = 1; t = 2 * n + 2;_for(i, 1, n){scanf("%d%d", &l[i], &r[i]);lsh[++cnt] = l[i];lsh[++cnt] = r[i];}sort(lsh + 1, lsh + cnt + 1);m = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;add(s, s2, k, 0);_for(i, 1, n){int ll = id(l[i]), rr = id(r[i]);add(ll, rr, 1, -(r[i] - l[i]));add(s2, ll, 1, 0);add(rr, t, 1, 0);_for(j, 1, n)if(r[i] <= l[j])add(id(r[i]), id(l[j]), 1, 0);}
}int main()
{build();ll mincost = 0;while(spfa()){mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}printf("%lld\n", -mincost);return 0;
}
P4012 深海机器人问题(费用流)
难得独立想出
一个机器人就是一条流,边权为标本的价值
注意要连两条边,一条容量为1费用为负权值,一条容量为正无穷费用为0
表示有一个机器人拿到标本,其他机器人经过这里对答案没有贡献
这样也保证了机器人一定能到终点
这个坐标不是传统的行列,而是直角坐标系
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e3 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], lsh[N], l[N], r[N], cnt;
int P, Q, s, t;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}int id(int i, int j) { return i + j * (Q + 1); }void build()
{int a, b;scanf("%d%d%d%d", &a, &b, &P, &Q);s = id(Q, P) + 1; t = id(Q, P) + 2;_for(j, 0, P)_for(i, 0, Q - 1){int x; scanf("%d", &x);add(id(i, j), id(i + 1, j), 1, -x);add(id(i, j), id(i + 1, j), 1e9, 0);}_for(i, 0, Q)_for(j, 0, P - 1){int x; scanf("%d", &x);add(id(i, j), id(i, j + 1), 1, -x);add(id(i, j), id(i, j + 1), 1e9, 0);}while(a--){int k, x, y; scanf("%d%d%d", &k, &y, &x);add(s, id(x, y), k, 0);}while(b--){int k, x, y; scanf("%d%d%d", &k, &y, &x);add(id(x, y), t, k, 0);}
}int main()
{build();ll mincost = 0;while(spfa()){mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}printf("%lld\n", -mincost);return 0;
}
P3355 骑士共存问题(最小割=最大流)
做了方格取数那道题这道题直接秒掉,这道题还更简单一些
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 4e4 + 10;
const int M = 200 + 10;
struct Edge { int from, to, flow; };
int dir[8][2] = {1, 2, 1, -2, -1, 2, -1, -2, 2, 1, -2, 1, 2, -1, -2, -1};
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N], a[M][M];
int n, m, s, t;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}int id(int i, int j) { return (i - 1) * n + j; }void build()
{scanf("%d%d", &n, &m);s = 0; t = id(n, n) + 1;_for(i, 1, m){int x, y;scanf("%d%d", &x, &y);a[x][y] = 1;}_for(i, 1, n)_for(j, 1, n){if(a[i][j]) continue;if((i + j) % 2 == 1){add(s, id(i, j), 1);rep(k, 0, 8){int x = i + dir[k][0], y = j + dir[k][1];if(x < 1 || x > n || y < 1 || y > n) continue;add(id(i, j), id(x, y), 1e9);}}else add(id(i, j), t, 1);}
}int main()
{build();int ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}printf("%d\n", n * n - m - ans);return 0;
}
还有一个思路是二分图最大独立集
把互斥表示为边,看最多可以选多少个点
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 4e4 + 10;
const int M = 200 + 10;
int dir[8][2] = {1, 2, 1, -2, -1, 2, -1, -2, 2, 1, -2, 1, 2, -1, -2, -1};
int a[M][M], vis[N], link[N], n, m;
vector<int> g[N];bool dfs(int u)
{for(int v: g[u]){if(vis[v]) continue;vis[v] = 1;if(!link[v] || dfs(link[v])){link[v] = u;return true;}}return false;
}int id(int i, int j) { return (i - 1) * n + j; }int main()
{scanf("%d%d", &n, &m);_for(i, 1, m){int x, y;scanf("%d%d", &x, &y);a[x][y] = 1;}_for(i, 1, n)_for(j, 1, n)if(!a[i][j] && (i + j) % 2 == 1)rep(k, 0, 8){int x = i + dir[k][0], y = j + dir[k][1];if(x < 1 || x > n || y < 1 || y > n || a[x][y]) continue; //注意这里跳到的点也要判可不可以放g[id(i, j)].push_back(id(x, y));}int ans = 0;_for(i, 1, n)_for(j, 1, n)if(!a[i][j] && (i + j) % 2 == 1){memset(vis, 0, sizeof vis);ans += dfs(id(i, j));}printf("%d\n", n * n - m - ans);return 0;
}
P3357 最长k可重线段集问题(下标处理)
这题和前面一题串并联的非常像,很快写完,然后有两个点T了
很快意识到存在线段垂直于x轴的情况,这样的话就会有自环,不行
尝试过很多处理方式,但是没AC
正确的处理方式是处理一下下标
首先全部乘以二,这样可以多了空间
对于相同的点,(x, x) 扩展成(2x,2x + 1)这样不会有自环
但是会带来一个问题,就是(x, y) 和(x, x)原本不相交
扩展后(2x, 2x+1) (2x, 2y)这样就相交了
解决方法是对于那些非垂直于x轴的线段左端点乘以2+1,右端点乘以2
可以验证一下对于 非垂直 和非垂直,垂直和非垂直,垂直和垂直都是可行的
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e3 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], lsh[N], val[N], l[N], r[N], self[N];
int n, k, s, s2, t, cnt;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(self, 0, sizeof(self));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(v == u){if(!self[u]){vis[v] = 1; q.push(v);self[u] = 1;}}else if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build()
{scanf("%d%d", &n, &k);s = 2 * n + 1; s2 = s + 1; t = s + 2;add(s, s2, k, 0);_for(i, 1, n){int y1, y2;scanf("%d%d%d%d", &l[i], &y1, &r[i], &y2);val[i] = sqrt(pow(r[i] - l[i], 2) + pow(y1 - y2, 2));if(l[i] > r[i]) swap(l[i], r[i]);if(l[i] == r[i]) l[i] *= 2, r[i] = (r[i] * 2 + 1);else l[i] = (l[i] * 2 + 1), r[i] *= 2;lsh[++cnt] = l[i]; lsh[++cnt] = r[i];}sort(lsh + 1, lsh + cnt + 1);cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;_for(i, 1, n){l[i] = lower_bound(lsh + 1, lsh + cnt + 1, l[i]) - lsh;r[i] = lower_bound(lsh + 1, lsh + cnt + 1, r[i]) - lsh;}_for(i, 1, n){add(s2, l[i], 1, 0);add(r[i], t, 1, 0);add(l[i], r[i], 1, -val[i]);_for(j, 1, n)if(r[i] <= l[j])add(r[i], l[j], 1, 0);}
}int main()
{build();ll mincost = 0;while(spfa()){mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}printf("%lld\n", -mincost);return 0;
}
还有一种更加通用的写法,拆点
既然要避免自己连向自己的情况,那就把一个点拆成一个入点和一个出点
首先是要一个点的入点和出点连一条容量为1费用0的边
然后连源点和汇点的时候都是对应的入点和出点
对于自身连边的时候,如果坐标同,就入点连向出点,容量1费用为权值
否则就左端点的出点连右端点的入点。相当于端点相同的时候中间的部分省去了
然后判断区间是否相交的时候要用出点和入点。
#include <bits/stdc++.h>
#define in(i) (i << 1)
#define out(i) (i << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 2e3 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], lsh[N], val[N], l[N], r[N];
int n, k, s, s2, t, cnt;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build()
{scanf("%d%d", &n, &k);s = 4 * n + 2; s2 = s + 1; t = s + 2;add(s, s2, k, 0);_for(i, 1, n){int y1, y2;scanf("%d%d%d%d", &l[i], &y1, &r[i], &y2);val[i] = sqrt(pow(r[i] - l[i], 2) + pow(y1 - y2, 2));if(l[i] > r[i]) swap(l[i], r[i]);lsh[++cnt] = l[i]; lsh[++cnt] = r[i];}sort(lsh + 1, lsh + cnt + 1);cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;_for(i, 1, n){l[i] = lower_bound(lsh + 1, lsh + cnt + 1, l[i]) - lsh;r[i] = lower_bound(lsh + 1, lsh + cnt + 1, r[i]) - lsh;}_for(i, 1, cnt) add(in(i), out(i), 1, 0);_for(i, 1, n){add(s2, in(l[i]), 1, 0);add(out(r[i]), t, 1, 0);if(l[i] == r[i]) add(in(l[i]), out(r[i]), 1, -val[i]);else add(out(l[i]), in(r[i]), 1, -val[i]);_for(j, 1, n)if(out(r[i]) <= in(l[j]))add(out(r[i]), in(l[j]), 1, 0);}
}int main()
{build();ll mincost = 0;while(spfa()){mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}printf("%lld\n", -mincost);return 0;
}
周四
P1251 餐巾计划问题(建立模型)
这题大部分都很好相,我卡在不知道怎么限制这个最大流从而满足题目说的每天用x块餐巾
正确方法是从源点向出点连容量x费用0,表示获得x块旧餐巾,入点向汇点连x,表示消耗x块新餐巾
这里连向汇点的一定会流满,所以也就对应了题目所说的每天消耗x块新餐巾
一块餐巾看作一股流,源点流来表示有新的,流向汇点表示消耗了
#include <bits/stdc++.h>
#define in(i) (i << 1)
#define out(i) (i << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 4e3 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], n, m, s, t;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build()
{scanf("%d", &n);s = 0; t = out(n) + 1;_for(i, 1, n){int x; scanf("%d", &x);add(s, out(i), x, 0);add(in(i), t, x, 0);}int a, b, c, d, e;scanf("%d%d%d%d%d", &a, &b, &c, &d, &e);_for(i, 1, n){add(s, in(i), 1e9, a);if(i + 1 <= n) add(out(i), out(i + 1), 1e9, 0);if(i + b <= n) add(out(i), in(i + b), 1e9, c);if(i + d <= n) add(out(i), in(i + d), 1e9, e);}
}int main()
{build();ll mincost = 0;while(spfa()){mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}printf("%lld\n", mincost);return 0;
}
P4013 数字梯形问题(费用流)
独立做出,开心
这题和最长不下降子序列的思路很相似
就是一层一层的,然后通过修改容量来控制这个点可以经过多少次
#include <bits/stdc++.h>
#define in(i) (i << 1)
#define out(i) (i << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 5e3 + 10;
const int M = 50;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], a[N][N], id[N][N], cnt;
int n, m, s, t;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build1()
{_for(i, 1, m) add(s, in(id[1][i]), 1, 0);_for(i, 1, n)_for(j, 1, m + i - 1){add(in(id[i][j]), out(id[i][j]), 1, -a[i][j]);if(i != n){add(out(id[i][j]), in(id[i + 1][j]), 1, 0);add(out(id[i][j]), in(id[i + 1][j + 1]), 1, 0);}else add(out(id[i][j]), t, 1, 0);}
}void build2()
{edge.clear();_for(i, s, t) g[i].clear();_for(i, 1, m) add(s, in(id[1][i]), 1, 0);_for(i, 1, n)_for(j, 1, m + i - 1){add(in(id[i][j]), out(id[i][j]), 1e9, -a[i][j]);if(i != n){add(out(id[i][j]), in(id[i + 1][j]), 1, 0);add(out(id[i][j]), in(id[i + 1][j + 1]), 1, 0);}else add(out(id[i][j]), t, 1e9, 0);}
}void build3()
{edge.clear();_for(i, s, t) g[i].clear();_for(i, 1, m) add(s, in(id[1][i]), 1, 0);_for(i, 1, n)_for(j, 1, m + i - 1){add(in(id[i][j]), out(id[i][j]), 1e9, -a[i][j]);if(i != n){add(out(id[i][j]), in(id[i + 1][j]), 1e9, 0);add(out(id[i][j]), in(id[i + 1][j + 1]), 1e9, 0);}else add(out(id[i][j]), t, 1e9, 0);}
}ll solve()
{ll mincost = 0;while(spfa()){mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}return -mincost;
}int main()
{scanf("%d%d", &m, &n);_for(i, 1, n)_for(j, 1, m + i - 1){scanf("%d", &a[i][j]);id[i][j] = ++cnt;}s = 0; t = out(cnt) + 1;build1();printf("%lld\n", solve());build2();printf("%lld\n", solve());build3();printf("%lld\n", solve());return 0;
}
P3356 火星探险问题
这题和深海机器人那题特别像
不同在于那题的权值是点与点之间的边,这题的权值是点
既然权值是点,那么就拆点,一个点拆成入点和出点,中间连一条费用为权值的边
方案输出的话可以遍历所有的边,然后用一个vector存一下
输出一条路径就在vector中删除这条路径
#include <bits/stdc++.h>
#define in(i) (i << 1)
#define out(i) (i << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 4e3 + 10;
const int M = 40;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N], pre[N];
int p[N], d[N], vis[N], a[M][M], cnt;
int n, m, k, s, t;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}int id(int i, int j) { return (i - 1) * m + j; }void build()
{scanf("%d%d%d", &k, &m, &n);s = 0; t = out(id(n, m)) + 1;add(s, in(id(1, 1)), k, 0);_for(i, 1, n)_for(j, 1, m)scanf("%d", &a[i][j]);_for(i, 1, n)_for(j, 1, m)if(a[i][j] != 1){add(in(id(i, j)), out(id(i, j)), 1e9, 0);if(a[i][j] == 2) add(in(id(i, j)), out(id(i, j)), 1, -1);if(i + 1 <= n && a[i + 1][j] != 1) add(out(id(i, j)), in(id(i + 1, j)), 1e9, 0);if(j + 1 <= m && a[i][j + 1] != 1) add(out(id(i, j)), in(id(i, j + 1)), 1e9, 0);}add(out(id(n, m)), t, 1e9, 0);
}void print(int now)
{if(!pre[now].size()) return;int temp = pre[now].back();print(temp);if(now - temp == m) printf("%d 0\n", cnt);else printf("%d 1\n", cnt);pre[now].erase(pre[now].end() - 1);
}int main()
{build();while(spfa())for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}for(auto e: edge)if(e.from < e.to && e.from != s && e.to != t && e.from / 2 != e.to / 2)_for(i, e.flow, 1e9 - 1) pre[e.to / 2].push_back(e.from / 2);while(pre[id(n, m)].size()){cnt++;print(id(n, m));}return 0;
}
P2770 航空路线问题
独立做出
首先转化一下,把返回的那条路线看作由起点到终点的
也就是看是否存在只有首尾相交的两条路径
那么一个点拆成入点和出点,中间连的边的容量代表其这个点可以经过多少次
首尾2次,中间一次
有连接的点,这个点的出点连到另外一点的入点
因为只有一条流,所以容量为1
跑费用流就行了
这里有个坑,就是有一种情况起点直接到终点,这个时候两条路径是重合的
我们特判一下就好了,当最大流为1的时候,若首尾存在路径,就是可行的
#include <bits/stdc++.h>
#define in(i) (i << 1)
#define out(i) (i << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 200 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], pre[N];
int n, m, s, t, flag;
string str[N];
map<string, int> mp;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build()
{scanf("%d%d", &n, &m);s = 0; t = out(n) + 1;_for(i, 1, n){cin >> str[i];mp[str[i]] = i;}while(m--){string s1, s2;cin >> s1 >> s2;int u = mp[s1], v = mp[s2];if(u > v) swap(u, v);if(u == 1 && v == n) flag = 1;add(out(u), in(v), 1, 0);}add(s, in(1), 2, 0);add(in(1), out(1), 2, 0);_for(i, 2, n - 1) add(in(i), out(i), 1, -1);add(in(n), out(n), 2, -1);add(out(n), t, 2, 0);
}void print(int now)
{if(!now) return;print(pre[now]);cout << str[now] << endl;
}int main()
{build();ll mincost = 0, maxflow = 0;while(spfa()){maxflow += f[t];mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}if(maxflow != 2){if(flag){puts("2");cout << str[1] << endl;cout << str[n] << endl;cout << str[1] << endl;}else puts("No Solution!");return 0;}cout << -mincost << endl;int temp;for(auto e: edge)if(e.from < e.to && e.from != s && e.to != t && e.from / 2 != e.to / 2 && !e.flow){if(pre[e.to / 2]) temp = e.from / 2;else pre[e.to / 2] = e.from / 2;}print(temp);cout << str[n] << endl;for(int now = pre[n]; now; now = pre[now])cout << str[now] << endl;return 0;
}
P2754 [CTSC1999]家园 / 星际转移问题
独立做出,开心
感觉学懂了一些网络流的套路了
这道题流就是人
然后用到了分层,每一个时间点就是一层。直接枚举层数,判断最大流是否为k
第一次达到k时这个时间点就是答案
无解的话只有一种情况,就是地球和月球不联通
用并查集判断一下就好了
然后注意,每次新加了点和边,直接在之前的残量网络上跑就行,不需要重新建图
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e5 + 10;
const int M = 25;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N], p[M];
int d[N], cur[N];
int n, m, k, s, t, loop = 1;
int h[M], r[M], f[N];void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }
int lcm(int a, int b) { return a / gcd(a, b) * b; }int find(int x)
{if(f[x] == x) return x;return f[x] = find(f[x]);
}void build()
{scanf("%d%d%d", &n, &m, &k);s = 1e5; t = 1e5 + 1;_for(i, 0, t) f[i] = i;add(s, 0, k);_for(i, 1, m){scanf("%d%d", &h[i], &r[i]);loop = lcm(loop, r[i]);_for(j, 1, r[i]){int x; scanf("%d", &x);if(x == -1) x = t;p[i].push_back(x);int temp = p[i].size();if(temp > 1)f[find(p[i][temp - 1])] = find(p[i][temp - 2]);}}
}int id(int i, int j) { return i * (n + 1) + j; } //时间为i第j个点int main()
{build();if(find(0) != find(t)){puts("0");return 0;}int time = 0, sum = 0;while(1){_for(i, 0, n) add(id(time, i), id(time + 1, i), 1e9);_for(i, 1, m){int pre = p[i][time % r[i]];int now = p[i][(time + 1) % r[i]];if(pre != t){if(now == t) add(id(time, pre), t, h[i]);else add(id(time, pre), id(time + 1, now), h[i]);}}time++;while(bfs()){memset(cur, 0, sizeof(cur));sum += dfs(s, 1e18);}if(sum == k){printf("%d\n", time);break;}}return 0;
}
原本以为还剩下一题,结果那题连题解都没有,是迭代加深搜索,和网络流没关系……
突然就刷完了24题了。自己对一些套路都熟悉了很多,挺好的,挺开心的。
明天到cf里面补一补之前模拟赛的三道题
接下来一个星期左右大量刷dp
周五
先补几道模拟赛的题
H. Combination Lock(二分图博弈)
这个当时想了很久没什么想法,没想到是一道模板题
在一个二分图上,在某个点开始,两个人轮流选边到另一个点
选过的点不能再选。谁无法再选就输了
这个就是二分图博弈问题
结论是当起始点一定在最大匹配中,先手获胜,否则先手必败。注意是一定
证明是,如果起始点不在二分图匹配中,那么首先选一条边到一个点,这个点一定是匹配点,否则就有新的匹配边,就不是最大匹配了
然后后手选匹配边。先手再选,这时先手依然选到的一定是匹配点,否则就有增广路,也不是最大匹配。这样一直下去,所有的匹配边都遍历完了之后,先手就选不了,先手输了
判断方法用二分图匹配。包含起始点和不包含起始点都做一次二分图匹配,如果匹配数不变,说明起始点可有可无,否则起始点一定在最大匹配中
这道题数据范围大用Dinic
同时不需要跑两次,先不包含起始点跑,然后加入新的边,在残量网络中再跑就行了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e5 + 10;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
unordered_map<int, bool> mp;
int d[N], cur[N];
int n, m, s, t, start;void add(int from, int to, int flow)
{edge.push_back(Edge{from, to, flow});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0});g[to].push_back(edge.size() - 1);
}bool bfs()
{memset(d, 0, sizeof(d));queue<int> q;q.push(s);d[s] = 1;while(!q.empty()){int u = q.front(); q.pop();for(auto x: g[u]){Edge e = edge[x];if(!d[e.to] && e.flow){d[e.to] = d[u] + 1;q.push(e.to);}}}return d[t];
}ll dfs(int u, ll in)
{if(u == t) return in;ll out = 0;for(int& i = cur[u]; i < g[u].size(); i++){Edge& e = edge[g[u][i]];if(d[u] + 1 == d[e.to] && e.flow){ll f = dfs(e.to, min((ll)e.flow, in));e.flow -= f;edge[g[u][i] ^ 1].flow += f;out += f; in -= f;if(in == 0) break;}}return out;
}vector<int> get(int now) //返回所有可以达到的状态
{vector<int> res;string s = to_string(now);int len = s.size();_for(i, len, m - 1) s = "0" + s;rep(i, 0, m)for(int k = -1; k <= 1; k += 2){string v = s;int temp = v[i] - '0' + k;if(temp == 10) temp = 0;else if(temp == -1) temp = 9;v[i] = temp + '0';int node = stoi(v);if(!mp[node] && node != start) res.push_back(node);}return res;
}int val(int now)
{int res = 0;while(now) { res += now % 10; now /= 10; }return res;
}void build()
{mp.clear(); edge.clear();scanf("%d%d%d", &m, &n, &start);s = 1e5; t = s + 1;_for(i, 0, t) g[i].clear();_for(i, 1, n){int x; scanf("%d", &x);mp[x] = 1;}_for(now, 0, pow(10, m) - 1)if(!mp[now] && now != start){if(val(now) % 2 == 1){add(s, now, 1);vector<int> v = get(now);for(auto x: v) add(now, x, 1);}else add(now, t, 1);}}int main()
{int T; scanf("%d", &T);while(T--){build();while(bfs()){memset(cur, 0, sizeof(cur));dfs(s, 1e18);}vector<int> v = get(start);if(val(start) % 2 == 1){add(s, start, 1);for(auto x: v) add(start, x, 1);}else{add(start, t, 1);for(auto x: v) add(x, start, 1);}ll ans = 0;while(bfs()){memset(cur, 0, sizeof(cur));ans += dfs(s, 1e18);}puts(ans ? "Alice" : "Bob");}return 0;
}
Direction Setting(费用流 + 边化点)
这道题再模拟赛时我们队卡住了
这道题用到了边化点。我做网络流24题的时候没有遇到这样处理的
这道题我卡在不知道怎么处理选择边的方向
方法是对于边u 到 v 多加一个点x,这个x向v连容量1费用0,向u连容量1费用0
源点向x连容量1费用0
这样子就很快的体现了选择边。这个时候一个度就是流
那么怎么统计答案呢
发现di <= ai都是没用贡献的
所以对于一个点,可以从这个点向汇点连一个容量ai费用0的边,再连容量无穷费用1的边
这样根据最小费用,一定是先使费用0的边满流,这就符合题目的条件
这里的最大流保证的都有选择方向
太秀了,网络流还是多积累吧
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e4 + 10;
struct Edge { int from, to, flow, cost; };
vector<Edge> edge;
vector<int> g[N];
int p[N], d[N], vis[N], e[N], n, m, s, t;
ll f[N];void add(int from, int to, int flow, int cost)
{edge.push_back(Edge{from, to, flow, cost});g[from].push_back(edge.size() - 1);edge.push_back(Edge{to, from, 0, -cost});g[to].push_back(edge.size() - 1);
}bool spfa()
{memset(vis, 0, sizeof(vis));memset(d, 0x3f, sizeof d);queue<int> q; q.push(s);d[s] = 0; f[s] = 1e18;vis[s] = 1;while(!q.empty()){int u = q.front(); q.pop();vis[u] = 0;for(auto x: g[u]){Edge e = edge[x];int v = e.to;if(e.flow && d[v] > d[u] + e.cost){d[v] = d[u] + e.cost;p[v] = x;f[v] = min(f[u], (ll)e.flow);if(!vis[v]) { vis[v] = 1; q.push(v); }}}}return d[t] < 1e9;
}void build()
{edge.clear();_for(i, 0, 1e4) g[i].clear();s = 1e4; t = 1e4 + 1;scanf("%d%d", &n, &m);_for(i, 1, n){int x; scanf("%d", &x);add(i, t, x, 0);add(i, t, 1e9, 1);}_for(i, 1, m){int u, v;scanf("%d%d", &u, &v);e[i] = edge.size();add(n + i, v, 1, 0);add(n + i, u, 1, 0);add(s, n + i, 1, 0);}
}int main()
{int T; scanf("%d", &T);while(T--){build();ll mincost = 0;while(spfa()){mincost += 1LL * f[t] * d[t];for(int u = t; u != s; u = edge[p[u]].from){edge[p[u]].flow -= f[t];edge[p[u] ^ 1].flow += f[t];}}printf("%lld\n", mincost);_for(i, 1, m) putchar(!edge[e[i]].flow ? '0' : '1');puts("");}return 0;
}
大致规划一下
单调队列复习加写题
数位dp复习加写题
队内dp题
斜率优化
dp杂题
单调队列模板
自己总结的
int l = 1, r = 0 / 1; //r = 0表示先加入再转移 r = 1表示先转移再加入(默认队列初始有一个0)_for(i, 1, n){if( ... > q[l] ) l++; //超出范围的出队//转移1 转移的范围不包括当前的iwhile(l <= r && val(a[r]) >= a[i]) r--; //保证单调性q[++r] = i;//转移2 转移的范围包括当前的i}//先转移再加入还可以r = 0, 一开始队列为空的时候特判 或者一开始初始化 看题目
P1776 宝物筛选(多重背包 + 二进制优化)
在单调队列优化dp里面的一道题,不知道为啥是背包……
二进制优化就完事了,很快打完,背包掌握的还可以
#include <bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 4e4 + 10;
struct node{ int v, w; };
vector<node> ve[N];
int n, m;
ll f[N];int main()
{scanf("%d%d", &n, &m);_for(i, 1, n){int v, w, k;scanf("%d%d%d", &v, &w, &k);int t = 1;while(1){if(k - t >= 0){ve[i].push_back(node{v * t, w * t});k -= t;t <<= 1;}else{ve[i].push_back(node{v * k, w * k});break;}}}_for(i, 1, n)for(auto x: ve[i]){int w = x.w, v = x.v;for(int j = m; j >= w; j--)f[j] = max(f[j], f[j - w] + v);}printf("%lld\n", f[m]);return 0;
}
P1613 跑路(floyed + 倍增)
首先n <= 50 最短路 想到floyed
这道题就是在最短路的基础上加上了一条如果距离为2的k次方那么可以缩小为1
所以我们就找到所有这样的边缩小为1即可
倍增的时候2个2的k次方可以转移到2的k+1次方
所以就按照次方的递增来
用floyed预处理就好
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 60;
const int M = 30;
int dis[N][N], g[N][N][M], n, m;int main()
{memset(dis, 0x3f, sizeof dis);scanf("%d%d", &n, &m);while(m--){int u, v;scanf("%d%d", &u, &v);dis[u][v] = 1;g[u][v][0] = 1;}rep(t, 1, M)_for(k, 1, n)_for(i, 1, n)_for(j, 1, n){g[i][j][t] |= g[i][k][t - 1] && g[k][j][t - 1]; //注意这里是 |= 存在一个k即可if(g[i][j][t]) dis[i][j] = 1;}_for(k, 1, n)_for(i, 1, n)_for(j, 1, n)dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);printf("%d\n", dis[1][n]);return 0;
}
今天有点疲惫,发现自己已经连续肝了八天了,每天大量刷题。
晚上休息休息
周日
昨天休息,摸了一天鱼
罗翔老师的课程真的太吸引人了哈哈哈
今天感觉精力恢复了,从今天开始再连肝个七八天
今天就状压dp 单调队列优化dp 数位dp都刷几题
P1441 砝码称重(dfs + dp)
这种dfs枚举情况,处理结果时dp第一次做。做的题太少了
正解对比我原来的想法有两个优化
我开始的想法是先二进制枚举出所有情况
而其实就这道题而言,dfs是更快的,因为在枚举的过程中可以剪枝
其次是枚举出情况后怎么处理
我开始的想法也是暴力二进制枚举
这样很慢,而其实可以用01背包来,每个物品就是放与不放,这样快很多
用01背包算出这些重量组合起来有多少种可能的值
还有一个小地方,就是我一开始枚举重量的时候从最大重量开始枚举
有一个优化就是处理出当前物品所能组成的最大重量,从这开始枚举,这样更快
当然这时建立在判断存不存在,最大重量由重量之和决定才这么写。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 25;
const int M = 2000 + 10;
int a[N], f[M], choose[N], ans, n, m;void dp()
{int res = 0, sum = 0;memset(f, 0, sizeof f);f[0] = 1;_for(i, 1, n)if(choose[i]){sum += a[i];for(int j = sum; j >= a[i]; j--)if(!f[j] && f[j - a[i]]){f[j] = 1;res++;}}ans = max(ans, res);
}void dfs(int cur, int del)
{if(del > m) return;if(cur == n + 1){if(del == m) dp();return;}choose[cur] = true;dfs(cur + 1, del);choose[cur] = false;dfs(cur + 1, del + 1);
}int main()
{scanf("%d%d", &n, &m);_for(i, 1, n) scanf("%d", &a[i]);dfs(1, 0);printf("%d\n", ans);return 0;
}
P3694 邦邦的大合唱站队(状压dp)
首先M很小,只有20,看到这个数据范围要想到状压dp
但是我自己想的时候,不知道该怎么安排顺序,就是选了这些乐队的时候,哪个在前哪个在后,就卡住了
看了题解发现原来顺序是没有后效性的,当前的顺序并不影响下一个乐队所需要出去的人数
因为既然哪些乐队已经选定了,那么总人数就定了,下一个乐队只需要接在这些乐队后面,这时其位置是决定的了,不管前面乐队顺序如何。这是关键。满足了动态规划的无后效性。这个状态已经表示了最优答案,且不影响后面的状态
理解了这个就很简单了,第一层循环枚举每一个状态,第二层循环枚举每一个乐队就行了
同时要初始化一些东西
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e5 + 10;
const int M = 25;
int dp[1 << 20], s[N][M], num[M], n, m;int main()
{scanf("%d%d", &n, &m);_for(i, 1, n){int x; scanf("%d", &x);_for(j, 1, m) s[i][j] = s[i - 1][j];s[i][x]++;num[x]++;}memset(dp, 0x3f, sizeof dp);dp[0] = 0;rep(S, 0, 1 << m){int sum = 0;_for(i, 1, m)if(S & (1 << (i - 1)))sum += num[i];_for(i, 1, m)if(S & (1 << (i - 1)))dp[S] = min(dp[S], dp[S ^ (1 << (i - 1))] + num[i] - (s[sum][i] - s[sum - num[i]][i]));}printf("%d\n", dp[(1 << m) - 1]);return 0;
}
P1879 [USACO06NOV]Corn Fields G(状压dp)
独立做出,开心
枚举这一行的状态和上一行的状态,符合条件的就把上一行的方案数加到这一行,求和就好了
这里面的一些位运算我用到了计算机系统课里面的用真值表画逻辑电路的思路。利用取反和与操作来判断
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int mod = 1e8;
const int M = 15;
int dp[M][1 << 12], a[M], n, m;
vector<int> ve[M];int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }int main()
{scanf("%d%d", &n, &m);_for(i, 1, n)_for(j, 1, m){int x; scanf("%d", &x);a[i] = (a[i] << 1) + x;}_for(i, 1, n)rep(S, 0, 1 << m)if(!((S & (S << 1)) || (S & (~a[i]))))ve[i].push_back(S);for(int S: ve[1]) dp[1][S] = 1;_for(i, 2, n)for(int pre: ve[i - 1])for(int cur: ve[i]){if(pre & cur) continue;dp[i][cur] = add(dp[i][cur], dp[i - 1][pre]);}int ans = 0;for(int cur: ve[n])ans = add(ans, dp[n][cur]);printf("%d\n", ans);return 0;
}
P3092 [USACO13NOV]No Change G(状压dp + 尺取法)
我在对答案的统计上面有点想复杂了
其实就是看有多少种选法可以选完,然后剩下没选的硬币之和就是答案
我想的时候考虑了每次买的时候会浪费多少钱,要尽可能少浪费钱。然而这样想dp就很难弄
直接从结果出发,看当前这个状态能不能买完,然后剩下的硬币之和就是答案。
既然这样,那么状态设计就是当前状态最多能买多少商品
同样枚举某个商品不买,然后转移
这时是没有后效性的,前面的状态已经是最多的商品,已经是最优的。之前状态的dp值已经代表了那个状态
其中要预处理一下当前商品在某个位置最多能买到哪个位置
暴力必超时,可以二分,又发现可以用尺取法,更快。因为硬币的值不会是负数
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;typedef long long ll;
const int N = 1e5 + 10;
const int M = 20;
int dp[1 << 16], f[M][N], v[M], a[N], n, m;int main()
{scanf("%d%d", &m, &n);_for(i, 1, m) scanf("%d", &v[i]);_for(i, 1, n) scanf("%d", &a[i]);_for(i, 1, m){int l = 1, r = 0, sum = 0;while(l <= n){while(r < n && sum + a[r + 1] <= v[i]) sum += a[++r];f[i][l] = r;sum -= a[l++];}}rep(S, 0, 1 << m){_for(i, 1, m)if(S & (1 << (i - 1)))dp[S] = max(dp[S], f[i][dp[S ^ (1 << (i - 1))] + 1]);}ll ans = -1;rep(S, 0, 1 << m)if(dp[S] == n){ll sum = 0;_for(i, 1, m)if(!(S & (1 << (i - 1))))sum += v[i];ans = max(ans, sum);}printf("%lld\n", ans);return 0;
}