费用流模板 LOJ
LOJ上模板68ms还挺快
要跑一遍最大费用最大流和最小费用最大流 每次跑之前重新建图 范围很小没什么问题
每次SPFA找出最短/最长路 然后blabla 把费用W当成边权
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 5007;
#define Min(_A,_B) (_A>_B?_B:_A)
const int inf = 0x3f3f3f3f;
struct edge {
int v, c, w, nxt;
}e[maxn << 1];
int head[maxn], eid = 0, d[maxn], pre[maxn], s, t, a[101], b[101], n, m, h[101][101];
bool inq[maxn];
//带权二分图匹配
void insert(int u, int v, int c, int w) {
e[eid].v = v; e[eid].w = w; e[eid].c = c;e[eid].nxt = head[u]; head[u] = eid++;
e[eid].v = u; e[eid].nxt = head[v]; e[eid].w = -w; e[eid].c = 0; head[v] = eid++;
}
bool spfa() {
memset(inq, 0, sizeof(inq));
memset(d, -inf, sizeof(d));
memset(pre, -1, sizeof(pre));
queue<int> q;
q.push(s);
inq[s] = true;
d[s] = 0;
int u, v, i;
while (!q.empty()) {
u = q.front(); q.pop();
inq[u] = false;
for (i = head[u]; ~i; i = e[i].nxt) {
if (e[i].c) {
v = e[i].v;
if (d[u] + e[i].w>d[v]) {//最长路解决最大费用最大流问题
d[v] = d[u] + e[i].w;
pre[v] = i;
if (!inq[v]) {
inq[v] = true;
q.push(v);
}
}
}
}
}
return pre[t] != -1;
}
bool spfa_min() {
memset(inq, 0, sizeof(inq));
memset(d, inf, sizeof(d));
memset(pre, -1, sizeof(pre));
queue<int> q;
q.push(s);
inq[s] = true;
d[s] = 0;
int u, v, i;
while (!q.empty()) {
u = q.front(); q.pop();
inq[u] = false;
for (i = head[u]; ~i; i = e[i].nxt) {
if (e[i].c) {
v = e[i].v;
if (d[u] + e[i].w<d[v]) {//最短路解决最小费用最大流问题
d[v] = d[u] + e[i].w;
pre[v] = i;
if (!inq[v]) {
inq[v] = true;
q.push(v);
}
}
}
}
}
return pre[t] != -1;
}
int costflow() {
int res = 0;
while (spfa()) {
int i, flow = inf;
for (i = t; i != s; i = e[pre[i] ^ 1].v) {
flow = Min(flow, e[pre[i]].c);//寻找路径上的最小流量
}
for (i = t; i != s; i = e[pre[i] ^ 1].v) {
e[pre[i] ^ 1].c += flow;
e[pre[i]].c -= flow;
res += e[pre[i]].w*flow;
}
}
return res;
}
int costflow_min() {
int res = 0;
while (spfa_min()) {
int i, flow = inf;
for (i = t; i != s; i = e[pre[i] ^ 1].v) {
flow = Min(flow, e[pre[i]].c);
}
for (i = t; i != s; i = e[pre[i] ^ 1].v) {
e[pre[i] ^ 1].c += flow;
e[pre[i]].c -= flow;
res += e[pre[i]].w*flow;
}
}
return res;
}
inline int read() {
int s = 0, f = 1; char c = getchar(); while (c<'0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
while (c >= '0'&&c <= '9') { s = s * 10 + c - '0'; c = getchar(); }
return s*f;
}
int main() {
m = read(); n = read(); int i, j;
memset(head, -1, sizeof(head));
s = 0; t = n+m+1;
for (i = 1; i <= m; i++) {
a[i] = read();
insert(s, i, a[i], 0);
}
for (i = 1; i <= n; i++) {
b[i] = read();
insert(i + m, t, b[i], 0);
}
for (i = 1; i <= m; i++)
for (j = 1; j <= n; j++) {
h[i][j] = read();
insert(i, j + m, inf, h[i][j]);
}
printf("%d\n", costflow_min());
eid = 0;
memset(head, -1, sizeof(head));
for (i = 1; i <= m; i++) insert(s, i, a[i], 0);
for (i = 1; i <= n; i++) insert(i + m, t, b[i], 0);
for (i = 1; i <= m; i++)
for (j = 1; j <= n; j++)
insert(i, j + m, inf, h[i][j]);
printf("%d", costflow());
//getchar();
return 0;
}
SCOI2007 修车
拆点 把每个师傅拆成n个点表示倒数第i个修的是xxx
比如师傅i按顺序修了三辆车,时间分别是T1,T2,T3,那么总时间是T1+(T1+T2) +(T1+T2+T3)
那么可以设倒数第i个修的车对应的弧费用为T*i
BZOJ上TLE了 时限1s过不去 但是洛谷AC了
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define Max(_A,_B) (_A>_B?_A:_B)
#define Min(_A,_B) (_A>_B?_B:_A)
const int inf = 0x3f3f3f3f;
using namespace std;
const int maxn = 370007;//10*60*60
int n, m;
struct edge {
int v, w, c, nxt;
}e[maxn<<1];
int p[maxn], eid = 0, dis[maxn],pre[maxn],s,t;
bool inq[maxn];
void insert(int u, int v, int c, int w) {
e[eid].v = v;
e[eid].w = w;
e[eid].c = c;
e[eid].nxt = p[u];
p[u] = eid++;
}
void addedge(int u, int v, int c, int w) {
insert(u, v, c, w);
insert(v, u, 0, -w);
}
bool spfa() {
queue<int> q;
q.push(s);
memset(dis, inf, sizeof(dis));
memset(pre, -1, sizeof(pre));
memset(inq, false, sizeof(inq));
dis[s] = 0;
inq[s] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
inq[u] = false;
for (int i = p[u]; i!=-1; i = e[i].nxt){
if (e[i].c) {
int v = e[i].v;
if (dis[u] + e[i].w < dis[v]) {
dis[v] = dis[u] + e[i].w;
pre[v] = i;
if (!inq[v]) {
inq[v] = true;
q.push(v);
}
}
}
}
}
return pre[t] != -1;
}
double cost_flow() {
double res = 0;
while (spfa()) {
int flow = inf;
for (int i = t; i != s; i = e[pre[i] ^ 1].v) {
flow = Min(flow, e[pre[i]].c);
//printf("1");
}
for (int i = t; i != s; i = e[pre[i] ^ 1].v) {
e[pre[i]].c -= flow;
e[pre[i] ^ 1].c += flow;
res += e[pre[i]].w*flow;
}
}
return res;
}
inline int read() {
int s = 0, f = 1; char c = getchar(); while (c<'0' || c>'9') { if (c == '-') f = -1; c = getchar(); }
while (c >= '0'&&c <= '9') { s = s * 10 + c - '0'; c = getchar(); }
return s*f;
}
int g[61][11];
int main() {
m = read();
n = read();
s = 0; t = m*n+n+1;
memset(p, -1, sizeof(p));
for(int i=1;i<=n;i++)
for (int j = 1; j <= m; j++) {
g[i][j] = read();
}
for (int i = 1; i <= n*m; i++) addedge(s, i, 1, 0);
for (int i = n*m + 1; i <= n*m + n; i++)
addedge(i, t, 1, 0);
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)//次序
for (int k = 1; k <= n; k++)//客人编号
addedge((i - 1)*n + j, m*n + k, 1, g[k][i] * j);
printf("%.2lf\n", cost_flow()/n );
//getchar();
return 0;
}
NOI2012 美食节
建图方法和SCOI2007修车一样 但是数据更强了需要动态连边
然后注意到可以不把每道菜拆成P[i]个点 而是把S点连向表示该到菜的边的容量设成P[i] 就可以沿这条弧增广P[i]次了
在费用流过程中计算边对应的厨师和菜的次序时取模炸了 被m=1的数据卡掉了 发现原来的取模方式无法算出厨师做的第$\sum P$道菜 要特判 调了好久/_ \
动态连边的原理是,如果最小费用最大流增广路经过了厨师i做的倒数第j道菜的点 之前就一定经过了倒数第j-1道菜
因为倒数第1道菜的对应弧容量最小 (按修车那题的建模方式)
时限有点紧 还可以在spfa的同时预处理出对应增广路的流量 记为incf
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define Min(_A,_B) (_A<_B?_A:_B)
const int maxn = 81007,inf=0x3f3f3f3f;
struct edge {
int v, w, c, nxt;
}e[500007];
int head[maxn], eid = 0, d[maxn],s,
t,n,m,p[maxn],u,v,pre[maxn],incf[maxn],sp;
bool inq[maxn];
int g[41][101];
void insert(int u, int v, int c, int w) {
e[eid].v = v;
e[eid].c = c;
e[eid].w = w;
e[eid].nxt = head[u];
head[u]=eid++;
}
void addedge(int u, int v, int c, int w) {
insert(u, v, c, w);
insert(v, u, 0, -w);
}
void init() {
memset(head, -1, sizeof(head));
eid = 0;
}
/*动态加边的思路是 如果增广路流经x->t 与x相连的菜
表示厨师j做的倒数第x-n-(j-1)*p道菜
根据此题的构图
厨师i做的第j道菜耗时g[j][i]
第j+1道菜耗时g[j+1][i]+g[j][i]
第j+2道菜耗时g[j+2][i]+g[j+1][i]+g[j][i]
总时长为g[j][i]*3+g[j+1][i]*2+g[j+2][i]
故我们设各个菜与S相连 容量为pi
厨师与t相连 每个厨师j占的点编号范围为
n+p*(j-1)+1到n+p*j
即把每个厨师拆成至多p个点。
有T[x]<T[x+1] 如果没有连向x 就不加连向x+1的边
*/
bool spfa() {
memset(d, inf, sizeof(d));
memset(inq, false, sizeof(inq));
memset(pre, -1, sizeof(pre));
queue<int> q;q.push(s);
d[s] = 0;inq[s] = true;incf[s] = inf;
while (!q.empty()) {
u = q.front(); q.pop();inq[u] = false;
for (int i = head[u]; ~i; i = e[i].nxt) {
if (e[i].c>0&&d[e[i].v] > d[u] + e[i].w) {
v=e[i].v;
d[v] = d[u] + e[i].w;pre[v] = i;
incf[v] = Min(incf[u], e[i].c);
if (!inq[v]) { inq[v] = true;q.push(v);}
}
}
}
return pre[t]!=-1;
}
int ans = 0,x;
void cost_flow() {
while(spfa()){
for(int i=t;i!=s;i=e[pre[i]^1].v){
e[pre[i]].c-=incf[t];
e[pre[i]^1].c+=incf[t];
}
ans+=d[t]*incf[t];
x=e[pre[t]^1].v;//增广路连向t的边(厨师拆出的点)
//获取厨师编号j:由x=n+(j-1)*p+k知,
//j=(x-n-1)/p+1
//厨师现在是倒数第k道菜
//k=(x-n-1)%p+1
int a=(x-n)/sp+1,b=(x-n+1)%sp;
if(!b) b=sp;
//printf("%d %d %d\n",x,a,b);
addedge(x+1,t,1,0);
for(int i=1;i<=n;i++){
addedge(i,x+1,1,g[i][a]*b);
}
}
}
inline int read(){
int s=0,f=1;char c=getchar();while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
return s*f;
}
int main(){
n=read();m=read();
s=0;
init();
for(int i=1;i<=n;i++){
p[i]=read();sp+=p[i];
addedge(s,i,p[i],0);//p[i]道菜匹配p[i]次
}
t=sp*m+n+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
g[i][j]=read();
//连接每个厨师做的倒数第一道菜
}
for(int j=1;j<=m;j++){
x=n+(j-1)*sp+1;//厨师j做的倒数第1道菜与x连
for(int i=1;i<=n;i++){
addedge(i,x,1,g[i][j]);
}
addedge(x,t,1,0);
}
//printf("%d\n",sp);
cost_flow();
printf("%d",ans);
return 0;
}