前置知识
网络流
一些常见的定义:
容量:每条边都有一个容量(水管的最大水流容量)
源点:出发点(水厂)。
汇点:结束点(废水站)。
流:一个合法解称作一个流,也就是一条可以从源点到汇点的一条合法路径。
流量:每条边各自被经过的次数称作其流量,最终收集的总数为整个流的流量。
Dinic (n^2*m)
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+5,inf=0x3f3f3f3f;
int n, m, s, t, x, y, z;
int level[N];//level是记录一个点有没有被遍历到|选取,如果最后t点不能被遍历到就结束
struct node
{
int link;
int w;
int position;
};
vector<node> f[N];
queue<int> q;
int dfs(int x,int flow)
{
int ans = 0;
if(x==t||flow==0)
return flow;
for(auto &i:f[x])//结构体无法在外部改变,外面只是创建了一个临时变量(相当结构体内部变量是私有属性)
{
if(i.w>0&&level[i.link]==level[x]+1)// 保证边权不为0 且按照深度dfs
{
int d = dfs(i.link, min(flow, i.w)); // 搜索,flow是最小的所以是min
if(d==0)
level[i.link] = 0;
if(d>0)
{
i.w -= d;//正向边减少
f[i.link][i.position].w += d;// 反向边增加
flow -= d;
ans += d;
if(flow==0)
break;
}
}
}
return ans;
}
bool bfs()
{
q.push(s);
memset(level, -1, sizeof(level));
level[s] = 0;
while(!q.empty())
{
int temp = q.front();
q.pop();
for(auto &i:f[temp])
{
if(i.w==0||level[i.link]!=-1)
continue;
level[i.link] = level[temp] + 1;
q.push(i.link);
}
}
if(level[t]!=-1)
return 1;
return 0;
}
long long dinic()
{
long long ans = 0;
while(bfs())
ans += dfs(s, inf);
return ans;
}
网络流模板
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+5,inf=0x3f3f3f3f;
int n, m, s, t, x, y, z;
int level[N];
struct node
{
int link;
int w;
int position;
};
vector<node> f[N];
queue<int> q;
int dfs(int x,int flow)
{
int ans = 0;
if(x==t||flow==0)
return flow;
for(auto &i:f[x])//结构体无法在外部改变,外面只是创建了一个临时变量(相当结构体内部变量是私有属性)
{
if(i.w>0&&level[i.link]==level[x]+1)// 保证边权不为0 且按照深度dfs
{
int d = dfs(i.link, min(flow, i.w)); // 搜索,flow是最小的所以是min
if(d==0)
level[i.link] = 0;
if(d>0)
{
i.w -= d;//正向边减少
f[i.link][i.position].w += d;// 反向边增加
flow -= d;
ans += d;
if(flow==0)
break;
}
}
}
return ans;
}
bool bfs()
{
q.push(s);
memset(level, -1, sizeof(level));
level[s] = 0;
while(!q.empty())
{
int temp = q.front();
q.pop();
for(auto &i:f[temp])
{
if(i.w==0||level[i.link]!=-1)
continue;
level[i.link] = level[temp] + 1;
q.push(i.link);
}
}
if(level[t]!=-1)
return 1;
return 0;
}
long long dinic()
{
long long ans = 0;
while(bfs())
ans += dfs(s, inf);
return ans;
}
int main()
{
scanf("%d %d %d %d", &n,&m,&s,&t);
for (int i = 1; i <= m;i++)
{
scanf("%d %d %d", &x, &y, &z);
f[x].push_back((node){y, z,(int)f[y].size()});
f[y].push_back((node){x, 0,(int)f[x].size()-1});// 获取x在y的第几条边
}
printf("%lld
", dinic());
return 0;
}
费用流
主要都是考建图(树上dp),没什么好说的(其实是说不清楚)
板子也就是ek上面改个spfa|dij 也有zkw费用流(dinic+spfa)
主要把网络流的思想理解就行了
The 2016 ACM-ICPC Asia Qingdao Regional Contest
G Coding Contest
题意:
N 个不相交区域,共有 M 条有向路连接两个区域
对于每个区域,给出 s[i],b[i]代表区域内有 s[i] 个人,b[i] 个餐包,一旦某人在本区域内拿不到餐包,就会前往其他区域获取餐包,
而众所周知,赛场上的路上是有很多电线的,一不小心就会踢到电线,所以现在每条路上都存在这一些电线,
现在已知,一旦某个选手走过第 i 条有向边,就有 p[i] 的概率踢到电线,进而影响整个电网,不过经过该路径的第一个人是必然不会踢到电线的,同时对于第 i 条边,限制最多 c[i] 个人走过。
现在,求整个电网被影响的最小概率。
(P=1-sum_{1}^{n}(1-p_i)^{flow[i]})
所以要P最小,后面那个求和最大,但是我们板子的mcost是mcost+=flow[i]*dis[i]
所以可以去对数变成这种形式(flow_i*log(1-p_i)) 也就是权值变成了(log(1-p_i)),然后跑最大花费(就是权值取反,其他不变)完事记得pow回来
但是第一个人一定不会踢到电线,所以我们第一次经过某个 '汇点' 的权值是0 将一条边拆为 add_edge(u,v,1,0)和 add_edge(u,v,c[i]-1,-log(1-p[i]))
直接用log精度居然爆了,学到了学到了
log2刚好是0.5 其他的都会爆炸
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+5,inf=0x3f3f3f3f;
int T,n, m, qx,zx,x,y,s[N],b[N],c[N];
int pre[N],flow[N],pos[N];
bool use[N];
double p[N],mcost,dis[N];
long long maxflow;
// pre记录回溯前驱,dis是 cost*w ,pre取代了level数组
struct node
{
int link;
int w;
double cost;
int position;
};
vector<node> f[N];
queue<int> q;
void add_edge(int u,int v,int w,double cost)
{
//建立u->v的正向边
f[u].push_back((node){v, w,cost,(int)f[v].size()});
f[v].push_back((node){u, 0,-cost,(int)f[u].size()-1});
// 注意花费是相反数
}
bool spfa()//最短路
{
while(!q.empty())
q.pop();
for (int i = 0; i <=n+1;i++)
{
dis[i] = inf;
use[i] = 0;
pre[i] = -1;
}
use[qx] = 1;
flow[qx] = inf;
dis[qx] = 0;
pre[qx] = 0;
q.push(qx);
while(!q.empty())
{
int temp = q.front();
q.pop();
use[temp] = 0;
for (int i = 0; i < f[temp].size();i++)
{
node xx = f[temp][i];
if(xx.w>0&&dis[xx.link]>dis[temp]+xx.cost+1e-18)
{
dis[xx.link] = dis[temp] + xx.cost;
pre[xx.link] = temp;
flow[xx.link] = min(flow[temp],xx.w);
pos[xx.link] = i;//记录这个点是temp的第几条边
if(use[xx.link]==0)
{
use[xx.link] = 1;
q.push(xx.link);
}
}
}
}
// for (int i = 0; i <= n+m+1;i++)
// printf("i=%d %d %d
", i,dis[i],pre[i]);
if (pre[zx] == -1)
return 0;
return 1;
}
void update()
{
int now = zx;
while(now!=qx)
{
node &temp = f[pre[now]][pos[now]];
temp.w -= flow[zx];
node &temp2=f[now][temp.position];
temp2.w+= flow[zx];
now = pre[now];
}
mcost += flow[zx] * dis[zx];
maxflow += flow[zx];
}
void mfmc()
{
qx = 0;
zx = n + 1;
memset(flow, 0, sizeof(flow));
mcost = 0;
maxflow = 0;
while(spfa())
{
update();
}
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d %d", &n,&m);
for (int i = 1; i <= n;i++)
f[i].clear();
for (int i = 1; i <= n;i++)
{
scanf("%d %d", &x,&y);
if(x>y)//需要流出人,所以相当于源点
{
add_edge(0, i, x - y, 0);
}
else//需要流入,相当于汇点
{
add_edge(i, n + 1, y - x, 0);
}
}
for (int i =1; i <= m;i++)
{
scanf("%d %d %d %lf",&x,&y,&c[i],&p[i]);
add_edge(x,y,c[i]-1, -(log(1-p[i])/log(2)));
add_edge(x, y, 1, 0); //第一个人cost是0
}
mfmc();
printf("%.2lf
", 1-pow(2,-mcost));
}
return 0;
}
/*
2
4 4
2 0
0 3
3 0
0 3
1 2 5 0.5
3 2 5 0.5
1 4 5 0.5
3 4 5 0.5
4 4
2 0
0 3
3 0
0 3
1 2 5 0.5
3 2 5 0.5
1 4 5 0.5
3 4 5 0.5
*/
最后附上dinic+spfa板子(n^2*m)再也不用ek了
#include<bits/stdc++.h>
using namespace std;
const int N = 1006,const_w=2e7+5;
const long long inf = 1e17;
int T,n, m, qx,zx,a,b,c,d,e,r[N];
int pre[N];
bool use[N];
long long mcost,dis[N],flow[N],maxflow;
// pre记录回溯前驱,dis是 cost*w ,pre取代了level数组
struct node
{
int link;
int w;
int cost;
int position;
};
vector<node> f[N];
queue<int> q;
void add_edge(int u,int v,int w,int cost)
{
//建立u->v的正向边
f[u].push_back((node){v, w,cost,(int)f[v].size()});
f[v].push_back((node){u, 0,-cost,(int)f[u].size()-1});
// 注意花费是相反数
}
bool spfa()//最短路
{
while(!q.empty())
q.pop();
for (int i = 0; i <=2*n+1;i++)
{
dis[i] = inf;
use[i] = 0;
pre[i] = -1;
}
use[qx] = 1;
flow[qx] = inf;
dis[qx] = 0;
pre[qx] = 0;
q.push(qx);
while(!q.empty())
{
int temp = q.front();
q.pop();
use[temp] = 0;
for (int i = 0; i < f[temp].size();i++)
{
node xx = f[temp][i];
if(xx.w>0&&dis[xx.link]>dis[temp]+xx.cost)
{
dis[xx.link] = dis[temp] + xx.cost;
pre[xx.link] = temp;
flow[xx.link] = min(flow[temp],(long long)xx.w);
if(use[xx.link]==0)
{
use[xx.link] = 1;
q.push(xx.link);
}
}
}
}
if (pre[zx] == -1)
return 0;
return 1;
}
long long dfs(int x,long long flow)
{
long long ans = 0;
if(x==2*n+1||flow==0)
return flow;
for(auto &i:f[x])//结构体无法在外部改变,外面只是创建了一个临时变量(相当结构体内部变量是私有属性)
{
if(i.w>0&&dis[i.link]==dis[x]+i.cost)// 保证边权不为0 且按照深度dfs
{
long long d = dfs(i.link, min(flow,(long long) i.w)); // 搜索,flow是最小的所以是min
if(d>0)
{
i.w -= d;//正向边减少
f[i.link][i.position].w += d;// 反向边增加
flow -= d;
ans += d;
mcost += d * i.cost; //比dinic多了一个这个
if(flow==0)
break;
}
}
}
return ans;
}
void mfmc()
{
qx = 0;
zx = 2*n + 1;
memset(flow, 0, sizeof(flow));
mcost = 0;
maxflow = 0;
while(spfa())
{
maxflow+=dfs(0,inf);
}
}
int main()
{
scanf("%d %d", &n,&m);
for (int i = 1; i <= m;i++)
{
scanf("%d %d %d", &a,&b,&c);
add_edge(a, b+n, 1, -c);
}
for (int i = 1; i <= n;i++)
{
add_edge(0, i, 1, 0);
add_edge(i + n, 2 * n + 1, 1, 0);
}
mfmc();
printf("%lld
",-mcost);
for (int i = 1; i <= n;i++)
{
for(auto j:f[i+n])
{
if(j.w!=0&&j.link!=2*n+1)
printf("%d ", j.link);
}
}
printf("
");
return 0;
}