1
求最小花费。
即将割边当做选择,
u
→
v
u\to v
u→v边割开表示不让
u
u
u和
v
v
v属于不同集合的花费。
考虑
s
→
a
→
t
s \to a \to t
s→a→t,如果流量是
1
1
1和
2
2
2,显然割左边的边,即花费
1
1
1留住左边。
这里在介绍,对于一个点被
A
A
A或
B
B
B选,容易建边。
但是如果
u
u
u和
v
v
v之间有限制,就直接建双向边即可。[1]和[2]都可以使用。
2
求最大贡献
即转换成总贡献
−
-
−可以不要的最小贡献(换句话说,另一种形式的花费)
洛谷P4210
题意
最小割问题,最大贡献。
在普通问题基础上,限制为
u
u
u和
v
v
v在同一集合有贡献,如果不在有负贡献。
题解
最大贡献转换成总贡献
−
-
−最小割(不要的贡献)
对于这个限制。
我们建边:
s
→
u
—
e
a
2
s\to u — \frac{e_a}{2}
s→u—2ea
u
→
t
—
e
b
2
u\to t — \frac{e_b}{2}
u→t—2eb
u
→
v
—
e
a
+
e
b
2
+
e
c
u\to v — \frac{e_a+e_b}{2}+e_c
u→v—2ea+eb+ec,双向边
首先,如果选择同一集合,最小割显然是
e
a
e_a
ea或者
e
b
e_b
eb。
否则,由于最后必然会留有对
v
a
v_a
va和
v
b
v_b
vb的边,所以一定会割掉一条中间的边。
在此基础上,保证最小割的话,就是割掉
e
a
2
\frac{e_a}{2}
2ea和
e
b
2
\frac{e_b}{2}
2eb,因为会有两条路走,一条路由于有
v
v
v在,所以只能割中间,另一条并不是,所以割掉更小的两边。
最后即转换成了
−
e
a
−
e
b
−
e
c
-e_a-e_b-e_c
−ea−eb−ec,所以初始总贡献为
e
a
+
e
b
e_a+e_b
ea+eb即能表示选择两个集合的情况。
e
a
+
e
b
+
v
a
1
+
v
a
2
+
v
b
1
+
v
b
2
e_a+e_b+v_a1+v_a2+v_b1+v_b2
ea+eb+va1+va2+vb1+vb2
e
a
+
e
b
+
e
c
e_a+e_b+e_c
ea+eb+ec
会不会割两边割完更优呢,根据这个,我们会发现,如果
e
c
>
∑
v
e_c>\sum v
ec>∑v,那么割两边更优。
但是如果发生了这样的情况,割一边全部的使得都在一个集合显然最优,例如
e
b
+
v
b
1
+
v
b
2
e_b+v_b1+v_b2
eb+vb1+vb2显然是较优解。
#include<bits/stdc++.h>
#define FOR(i,a,b) for(int i=a;i<=b;i++)
#define inf 0x3f3f3f3f
typedef long long ll;
using namespace std;
const int maxn=1e4+500;
struct Edge{
int from,to;
ll cap,flow;
};
struct Dinic{
int n,tmp,s,t;
vector<Edge>edges;
vector<int>G[maxn];//邻接表用,存储的是边在edges中的序号
bool vis[maxn];//BFS使用
int d[maxn];//从起点到i的距离
int cur[maxn];//当前弧下标
void init(int n,int s,int t){
this->n=n,this->s=s,this->t=t;
edges.clear();
for(int i=1;i<=n;i++)G[i].clear();
}
inline void AddEdge(int from,int to,ll cap){
edges.push_back((Edge){from,to,cap,0});
edges.push_back((Edge){to,from,0,0});
tmp=edges.size();
G[from].push_back(tmp-2);
G[to].push_back(tmp-1);
}
inline void add2(int from,int to,int x){
edges.push_back((Edge){from,to,x,0});
edges.push_back((Edge){to,from,x,0});
tmp=edges.size();
G[from].push_back(tmp-2);
G[to].push_back(tmp-1);
}
bool BFS(){
memset(vis,0,sizeof(vis));
queue<int>q;
q.push(s);
d[s]=0,vis[s]=1;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<G[x].size();i++){
Edge& e = edges[G[x][i]];
if(!vis[e.to]&&e.cap>e.flow){
vis[e.to]=1;
d[e.to]=d[x]+1;
q.push(e.to);
}//只考虑残量网络中的弧
}
}
return vis[t];//用于判断是否能走到底。
}
inline ll DFS(int x,ll a){//多路增广
if(x==t||a==0)return a;//a表示的是当前最小,也就是接下来能用的不能超过a
ll flow=0,f;
for(int& i=cur[x];i<G[x].size();i++){//能保证一个dfs中不重复走同样的边(对于同一个节点),因为走过的边一定是满载的了。
Edge& e = edges[G[x][i]];
if(d[x]+1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0){
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0)break;//f表示从这个e.to的点开始使用的最大流。
}
}
return flow;
}
ll Maxflow(){
ll flow=0;
while(BFS()){
memset(cur,0,sizeof(cur));
flow+=DFS(s,inf);
}
return flow;
}
}dc;
int main(){
int n,m,s=2;cin>>n>>m;
dc.init(n+2,n+1,n+2);
dc.AddEdge(n+1,1,inf);
dc.add2(1,n+2,1);
dc.AddEdge(n+1,n,1);
dc.AddEdge(n,n+2,inf);
for(int i=2;i<=n-1;i++){
int x;cin>>x;x*=2;
s+=x;
dc.AddEdge(n+1,i,x);
}
for(int i=2;i<=n-1;i++){
int x;cin>>x;x*=2;
s+=x;
dc.AddEdge(i,n+2,x);
}
for(int i=1;i<=m;i++){
int u,v,x,y,z;
scanf("%d%d%d%d%d",&u,&v,&x,&y,&z);
x*=2,y*=2,z*=2;
s+=x+y;
dc.AddEdge(n+1,u,x/2);
dc.AddEdge(n+1,v,x/2);
dc.AddEdge(u,n+2,y/2);
dc.AddEdge(v,n+2,y/2);
dc.add2(u,v,x/2+y/2+z);
}
int ans=(s-dc.Maxflow())/2;
cout<<ans<<endl;
}
对于题目给的,
1
1
1和
n
n
n已经被选择了。
需要提前建边,因为这里割表示不要,所以对于
1
1
1到
t
t
t流量是
1
1
1,
s
s
s到
1
1
1流量是
i
n
f
inf
inf。这样必然不要割后者,也就是
1
1
1一定在左边集合。