模板题HDU - 3549
最大流的精华在于反向边. (当然还是首选)
刘汝佳的(很快的, 一般的都能跑) (如果会T, 边的容量会爆int的, 小心的传inf即可, 能够就行)
int cnt ;
int n, m, s, t;
bool vis[maxn];
int d[maxn], cur[maxn];
struct Edge {
int u, v;
int cap, flow;
} e[maxn*6]; //因为是双向边 所以记得开二倍
vector<int> G[maxn];
void init() {
cnt = 0;
for (int i = 1 ; i <= n ; i ++) G[i].clear();
}
void add(int u, int v, int cap, int f) {
e[cnt] = Edge{u, v, cap, f};
}
void AddEdge(int u, int v, int cap) {
add(u, v, cap, 0);
G[u].push_back(cnt++);
add(v, u, 0, 0);
G[v].push_back(cnt++);
}
bool BFS() {
Fill(vis, 0);
queue<int> q; q.push(s);
vis[s] = 1; d[s] = 0;
while (!q.empty()) {
int v = q.front(); q.pop();
for (int i = 0; i < G[v].size(); i++) {
Edge &te = e[G[v][i]];
if (!vis[te.v] && te.cap > te.flow) { //只考虑残量网络的弧
vis[te.v] = 1;
d[te.v] = d[v] + 1;
q.push(te.v);
}
}
}
return vis[t];
}
int dfs(int x, int a) {
if (x == t || a == 0) return a;
int flow = 0, f;
for (int &i = cur[x]; i < G[x].size(); i++) { //从上次考虑的弧
Edge &te = e[G[x][i]];
if (d[x] + 1 == d[te.v] && (f = dfs(te.v, min(a, te.cap - te.flow))) > 0) {
te.flow += f;
e[G[x][i]^1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int Dinic() {
int flow = 0;
while (BFS()) {
Fill(cur, 0);
flow += dfs(s, inf);
}
return flow;
}
void solve() {
scanf("%d%d", &n, &m);
init();
for (int i = 1 ; i <= m ; i ++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
AddEdge(u, v, w);
}
s = 1, t = n;
printf("%d\n", Dinic());
}
优秀的邻接表写法 (玄学题)
const int maxn = 2e5+5;
struct node{
int u, v, cap, next;
}e[maxn*10];
int n, m, s, t;
int cnt, head[maxn];
int d[maxn];
void init() {
cnt =0 ; Fill(head, -1);
}
void add(int u, int v, int c) {
e[cnt] = node{u, v, c, head[u]};
head[u] = cnt++;
}
void AddEdge(int u, int v, int c) {
add(u, v, c);
add(v, u, 0);
}
int BFS() {
queue <int> q; Fill(d, -1);
d[s]=0; q.push(s);
while(!q.empty()) {
int u = q.front();
q.pop();
for(int i = head[u] ; ~i ; i = e[i].next) {
int to = e[i].v;
if(d[to] == -1 && e[i].cap > 0) {
d[to] = d[u]+1;
q.push(to);
}
}
}
return d[t] != -1;
}
int dfs(int u, int a) {
int r = 0;
if(u == t) return a;
for(int i = head[u] ; ~i && r < a ;i = e[i].next) {
int to = e[i].v;
if(e[i].cap > 0 && d[to] == d[u] + 1) {
int x = min(e[i].cap, a-r);
x = dfs(to, x);
r += x;
e[i].cap -= x; e[i^1].cap += x;
}
}
if(!r) d[u]=-2;
return r;
}
int Dinic()
{
int maxflow = 0, t;
while(BFS()) {
while(t = dfs(s, inf))
maxflow += t;
}
return maxflow;
}
void solve() {
scanf("%d%d", &n, &m);
init(); s = 0, t = 2*n + 1;
for (int i = 1 ; i <= m ; i ++) {
int u, v;
scanf("%d%d", &u, &v);
AddEdge(u, v+n, inf);
}
for (int i = 1 ; i <= n ; i ++) {
AddEdge(i+n, i, inf);
AddEdge(s, i, 1);
AddEdge(i+n, t, 1);
}
printf("%d\n", n - Dinic());
}
double的Dinic模板, 求最大密度子图( 注意比较大小都要有sgn! 不然各种错误!)
题目:华科决赛K题. 求n个数字中, 选择一些数字使得其中的pair数/这个数字集合size最大.
pair数定义为 (u, v) u % v == 0 || v % u == 0.
const int inf = 0x3f3f3f3f; //用这个可以直接mem
const ll INF = 1e18;
const int mod = 1e9+7;
const int maxn = 1e2+5;
const int maxm = 1e5 + 5;
const db eps = 1e-7;
const db eps2 = 1e-4;
int head[maxn], cnt;
int n, m, s, t;
bool vis[maxn], mark[maxn];
int d[maxn], cur[maxn], du[maxn];
struct Edge {
int u, v;
db cap, flow;
} e[maxm]; //因为是双向边 所以记得开二倍
vector<int> G[maxn];
int sgn(db x) {
return x < -eps ? -1 : x < eps ? 0 : 1;
}
void init() {
for (int i = 0; i < maxn; i++) G[i].clear();
cnt = 0;
}
void add(int u, int v, db cap, db f) {
e[cnt] = (Edge){u, v, cap, f};
}
void AddEdge(int u, int v, db cap) {
add(u, v, cap, 0);
G[u].push_back(cnt++);
add(v, u, 0, 0);
G[v].push_back(cnt++);
}
bool BFS() {
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
vis[s] = 1;
d[s] = 0;
while (!q.empty()) {
int v = q.front(); q.pop();
for (int i = 0; i < G[v].size(); i++) {
Edge &te = e[G[v][i]];
if (!vis[te.v] && sgn(te.cap - te.flow) > 0) { //只考虑残量网络的弧
vis[te.v] = 1;
d[te.v] = d[v] + 1;
q.push(te.v);
}
}
}
return vis[t];
}
db dfs(int x, db a) {
if (x == t || sgn(a) == 0) return a;
db flow = 0, f;
for (int &i = cur[x]; i < G[x].size(); i++) { //从上次考虑的弧
Edge &te = e[G[x][i]];
if (d[x] + 1 == d[te.v] && sgn(f = dfs(te.v, min(a, te.cap - te.flow))) > 0) {
te.flow += f;
e[G[x][i]^1].flow -= f;
flow += f;
a -= f;
if (sgn(a) == 0) break;
}
}
return flow;
}
db Dinic() {
db flow = 0;
while (BFS()) {
Fill(cur, 0);
flow += dfs(s, inf);
}
return flow;
}
struct node {
int u, v;
}e2[maxn*10];
bool check(db g) {
init();
for (int i = 1 ; i <= n ; i ++) {
AddEdge(s, i, m);
AddEdge(i, t, m+2*g-du[i]);
}
for (int i = 1 ; i <= m ; i ++) {
AddEdge(e2[i].u, e2[i].v, 1);
AddEdge(e2[i].v, e2[i].u, 1);
}
return sgn((1.0*n*m - Dinic()));
}
int a[maxn];
void solve() {
while(~scanf("%d", &n)) {
Fill(du, 0); s = 0, t = n + 1;
for (int i = 1 ; i <= n ; i ++) {
scanf("%d", a+i);
}
m = 0;
for (int i = 1 ; i <= n ; i ++) {
for (int j = i + 1 ; j <= n ; j ++) {
if (a[j] % a[i] == 0 || a[i] % a[j] == 0) {
++ du[i]; ++du[j];
e2[++m] = node{i, j};
}
}
}
db l = 0, r = n, mid, ans = 0;
while(sgn(l - r) < 0) {
mid = (l + r) / 2;
if (check(mid)) {
ans = mid;
l = mid;
}
else r = mid;
}
printf("%.8f\n", ans);
}
}
网络流的一些经典模型:
给定一个点带权的有向图,求这个图的最大权闭合图?
* 闭合图含义:图中任意的出边所指的点都在图中的点集内。
在原图点集的基础上增加源 和汇 s t ;将原图每条有向边的容量替换为INF,
然后s连所有权值为正的点,边的权值为点的权值,所有权值为负的点连t,权值为点权值的绝对值,
最后所有权值为正的和减去最小割即为所求
给定一个无向图,要求它的一个子图,使得子图中边数 |E| 与点数 |V| 的比值最大,即最大化:
|E||V| | E | | V |
只能说解法不好说, 详细参考胡伯涛《最小割模型在信息学竞赛中的应用》, 只能分析出是求这个问题, 和具体的建图方法即可, 剩下的就是套板子了(逃.
最小点覆盖指的是选择尽量少的点, 使得每条边至少有一个端点被选中. 那么在二分图匹配中很容易可以被证明就是该个二分图的最匹配数. 最小点权覆盖就是选择的这些点的点权和要尽量的小
将原图的边权赋为INF,然后增加超级源汇点,S连左半图,左半图连T,权值为点上的值, 跑最大流即可
最大独立集, 即选择尽量多的结点, 使得任意两个节点不相邻(即任意一条边的两个端点不会同时被选中), 最大独立集和最小点覆盖是互补的. 因此可以得出答案. 至于为什么是互补的, 就请好好想想. (白书P356)
并且这些点权和要尽量的大
所有节点的值减去二分图的最小点权覆盖