NOI2006最大获利
最大权闭合子图模板题。大概意思就是存在一个正权值点集
1. 连接
2. 连接
3. 连接
设最小割为
#include <bits/stdc++.h>
using namespace std;
struct node {
int to, next, dis, neg;
}edge[1000005];
int head[200005], top = 0;
void push(int i, int j, int d)
{
edge[++top].to = j;
edge[top].dis = d;
edge[top].next = head[i];
head[i] = top;
edge[top].neg = top+1;
edge[++top].to = i;
edge[top].dis = 0;
edge[top].next = head[j];
head[j] = top;
edge[top].neg = top-1;
}
int n, m;
int lev[200005], vis[200005], bfstime = 0;
queue<int> que;
int S, T;
void dfs_out(int nd, int tab );
bool bfs()
{
vis[S] = ++bfstime;
lev[S] = 1;
que.push(S);
int to, d;
while (!que.empty()) {
int nd = que.front(); que.pop();
for (int k = head[nd]; k; k = edge[k].next)
if (to = edge[k].to, vis[to] != bfstime && edge[k].dis){
vis[to] = bfstime;
lev[to] = lev[nd] + 1;
que.push(to);
}
}
//dfs_out(S, 0);
return vis[T] == bfstime;
}
int dfs(int nd, int maxflow)
{
if (nd == T || !maxflow) return maxflow;
int to, d, t, neg, ans = 0;
//cout << nd << " " << maxflow << endl;
for (int k = head[nd]; k; k = edge[k].next)
if (neg = edge[k].neg, to = edge[k].to, d = edge[k].dis, d && lev[to] == lev[nd] + 1) {
t = dfs(to, min(maxflow, d));
edge[k].dis -= t;
edge[neg].dis += t;
ans += t;
maxflow -= t;
}
if (maxflow) lev[nd] = -1;
return ans;
}
int dinic()
{
int ans = 0;
while (bfs())
ans += dfs(S, 1<<29);
return ans;
}
void dfs_out(int nd, int tab = 0)
{
if (nd) {
for (int i = 1; i <= tab; i++)
cout << ' ';
cout << nd << " -> ";
for (int i = head[nd]; i; i = edge[i].next)
if (edge[i].dis)
cout << edge[i].to << "(" << edge[i].dis << ") ";
cout << endl;
for (int i = head[nd]; i; i = edge[i].next)
if (edge[i].dis)
dfs_out(edge[i].to, tab+2);
}
}
int a[5005];
int main()
{
freopen("profit.in", "r", stdin);
freopen("profit.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
S = n+1, T = n+2;
for (int i = 1; i <= n; i++)
push(i, T, a[i]);
n = T;
int ans = 0;
for (int i = 1; i <= m; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
ans += c;
push(++n, a, 1<<29);
push(n, b, 1<<29);
push(S, n, c);
}
cout << ans - dinic() << endl;
return 0;
}
Luogu2711 小行星
由于有删除考虑最小割建模。
考虑只有二维的情况:显然可以用x和y坐标的所有可能值建图,存在一对
那么三维呢?
不妨将y拆成两个点
#include <bits/stdc++.h>
using namespace std;
// 1->500为x,501->1000为y1,1001-1500为y2,1501-2000为z
int g[2005][2005];
int S = 2001, T = 2002;
int n, a, b, c;
int vis[2005], bfstime = 0;
int lev[2005];
queue<int> que;
bool bfs()
{
vis[S] = ++bfstime;
lev[S] = 1;
for (que.push(S); !que.empty(); ) {
int t = que.front(); que.pop();
for (int k = 1; k <= 2002; k++) {
if (!g[t][k] || vis[k] == bfstime) continue;
vis[k] = bfstime;
lev[k] = lev[t] + 1;
que.push(k);
}
}
return vis[T] == bfstime;
}
int dfs(int nd, int maxflow = INT_MAX)
{
if (nd == T || !maxflow) return maxflow;
int t, ans = 0;
for (int k = 1; k <= 2002; k++) {
if (!g[nd][k] || lev[k] != lev[nd] + 1) continue;
t = dfs(k, min(maxflow, g[nd][k]));
ans += t;
maxflow -= t;
g[nd][k] -= t;
g[k][nd] += t;
}
if (maxflow) lev[nd] = -1;
return ans;
}
int dinic()
{
int ans = 0;
while (bfs()) {
ans += dfs(S);
}
return ans;
}
int main()
{
memset(g, 0, sizeof g);
memset(vis, 0, sizeof vis);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d%d", &a, &b, &c);
g[S][a] = g[1500+c][T] = 1;
g[a][500+b] = g[1000+b][1500+c] = 1<<20;
g[500+b][1000+b] = 1;
}
cout << dinic() << endl;
return 0;
}
luogu1402酒店之王
和上一题一样..
#include <bits/stdc++.h>
using namespace std;
// template......
// 1->100是房间,101->200是客人,201->300是客人2,之后是饭233
int main()
{
int n, p, q;
scanf("%d%d%d", &n, &p, &q);
memset(g, 0, sizeof g);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= p; j++) {
int a; scanf("%d", &a);
if (a) g[S][j] = 1, g[j][100+i] = 1<<20;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= p; j++) {
int a; scanf("%d", &a);
if (a) g[300+j][T] = 1, g[200+i][300+j] = 1<<20;
}
for (int i = 1; i <= n; i++)
g[100+i][200+i] = 1;
cout << dinic() << endl;
return 0;
}
SCOI2007 修车
第一道非模板费用流题!
费用流模型的建立往往通过最大流限制“完成任务的方式”,用费用完成最小化,比起最大流来说灵活性更大。
考虑这道题:不妨先用hzc神犇的“化动态为静态”的思维方式考虑计算“不计等待”的最小时间和:
S→Workeri,size=+∞,cost=0 Workeri→Carj,size=1,cost=timeij Carj→T,size=1,cost=0
这些条件分别限制了:每个工人修若干辆车,每辆车被修一次,最小化总时间。如何表示顺序呢?不妨将每一个工人拆成若干个“子工人”,
S→Workerik,size=1,cost=0 注:一个拆开后的工人只能修一辆车Workerik→Carj,size=1,cost=k×timeij
为什么是
#include <bits/stdc++.h>
using namespace std;
int g[605][605], c[605][605], inp[70][10];
int n, m;
int vis[605], dis[605], pre[605];
queue<int> que;
int S = 601, T = 602;
int spfa(int &cost)
{
memset(dis, 127/3, sizeof dis);
vis[S] = 1, dis[S] = 0;
for (que.push(S); !que.empty(); ) {
int t = que.front(); que.pop(); vis[t] = 0;
for (int i = 1; i <= 602; i++)
if (g[t][i] && dis[i] > dis[t] + c[t][i]) {
dis[i] = dis[t] + c[t][i];
pre[i] = t;
if (!vis[i])
vis[i] = 1, que.push(i);
}
}
if (dis[T] > 233333333) return -1;
int mf = 1e8;
for (int i = T; i != S; i = pre[i]) mf = min(mf, g[pre[i]][i]);
for (int i = T; i != S; i = pre[i]) g[pre[i]][i] -= mf, g[i][pre[i]] += mf;
cost += mf*dis[T];
return mf;
}
int dinic(int &cost)
{
int ans = 0, t; cost = 0;
while ((t = spfa(cost)) > 0)
ans += t;
return ans;
}
int main()
{
memset(g, 0, sizeof g);
memset(c, 0, sizeof c);
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &inp[i][j]);
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++) {// i号倒数第j个修k
for (int k = 1; k <= n; k++) {
g[(i-1)*n+j][m*n+k] = 1;
c[(i-1)*n+j][m*n+k] = j*inp[k][i];
c[m*n+k][(i-1)*n+j] = -j*inp[k][i];
g[m*n+k][T] = 1; // 每辆车被修一次
//c[m*n+k][T] = c[m*n+k][T] = 0;
}
g[S][(i-1)*n+j] = 1; // 一个拆点后的修理员只能修一辆车!
}
int f, cost;
f = dinic(cost);
printf("%.2f
", (double)cost/n);
return 0;
}
k取方格数
A此题可以获得若干倍经验加成…
考虑用费用流,将每一个点拆为入点和出点,考虑如下连边:
in→out,flow=∞,cost=0 in→out,flow=1,cost=−a[i]
这样构造既可以保证可以若干次经过一个点,又可以保证取点只获得一次人生的经验。
由于原图没有负权回路,而反向弧的权都是正的,因此不会有问题。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 4500, MAXM = 5500000;
struct node {
int to, next, f, c, neg;
} edge[MAXM];
int head[MAXN], top = 0;
void push(int i, int j, int k, int l)
{
//cout << "Push : " << i << " " << j << endl;
++top, edge[top] = (node) { j, head[i], k, l, top+1}, head[i] = top;
++top, edge[top] = (node) { i, head[j], 0, -l, top-1}, head[j] = top;
}
int dis[MAXN], vis[MAXN], pre[MAXN], pre_edge[MAXN], S = 4400, T = 4401;
queue<int> que;
int n, m, k;
bool spfa()
{
memset(dis, 127/3, sizeof dis);
memset(vis, 0, sizeof vis);
memset(pre, 0, sizeof pre);
dis[S] = 0, vis[S] = 1, que.push(S);
while (!que.empty()) {
int t = que.front(); que.pop(); vis[t] = 0;
for (int i = head[t]; i; i = edge[i].next) {
if (edge[i].f == 0 || dis[edge[i].to] <= dis[t]+edge[i].c) continue;
int to = edge[i].to, d = edge[i].c;
dis[to] = dis[t] + d;
pre[to] = t, pre_edge[to] = i;
if (!vis[to])
vis[to] = 1, que.push(to);
}
}
return dis[T] <= 233333333;
}
int mcf(int &cost)
{
int ans = INT_MAX;
for (int t = T; t != S; t = pre[t]) ans = min(ans, edge[pre_edge[t]].f);
for (int t = T; t != S; t = pre[t]) edge[pre_edge[t]].f -= ans, edge[edge[pre_edge[t]].neg].f += ans;
cost += dis[T]*ans;
return ans;
}
int work(int &cost)
{
int ans = 0;
while (spfa()) ans += mcf(cost);//, printf("%d", ans);
return ans;
}
const int inf = 233333333;
int a[101][101];
inline int number(int i, int j)
{ return m*(i-1)*2+(j-1)*2+1;}
int main()
{
scanf("%d%d%d", &k, &m, &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
push(number(i,j), number(i,j)+1, inf, 0), push(number(i,j), number(i,j)+1, 1, -a[i][j]);
if (i+1 <= n) push(number(i,j)+1, number(i+1, j), inf, 0);
if (j+1 <= m) push(number(i,j)+1, number(i, j+1), inf, 0);
}
push(S, number(1,1), k, 0);
push(number(n,m)+1, T, k, 0);
int cost = 0;
work(cost);
cout << -cost << endl;
return 0;
}