最小割
最小割应用——01规划问题
最优标号
给定一个无向图 G = ( V , E ) G=(V,E) G=(V,E),每个顶点都有一个标号,它是一个 [ 0 , 2 31 − 1 ] [0,2^{31}−1] [0,231−1] 内的整数。
不同的顶点可能会有相同的标号。
对每条边 (u,v),我们定义其费用 cost(u,v) 为 u 的标号与 v 的标号的异或值。
现在我们知道一些顶点的标号。
你需要确定余下顶点的标号使得所有边的费用和尽可能小。
- 对于每个点,只有两种取值,将其划分为两类,可应用01规划。
- 按位异或,对于二进制的每一位建图跑最大流,再将最后的结果相加
- 对于需要跑多遍的dinic(),单独存边,方便于每次建图
建边:
- 对于原图也已有的点, c ( u , v ) = 1 c(u,v)=1 c(u,v)=1
- 对于已知标号的点,为0 连S,为1 连T
void add(int u,int v,int w1,int w2){
e[++cnt].to =v; e[cnt].next=head[u]; e[cnt].w=w1; head[u]=cnt;
e[++cnt].to =u; e[cnt].next=head[v]; e[cnt].w=w2; head[v]=cnt;
}
void build (int x){
cnt=1; memset(head,0,sizeof head);
for(int i=1;i<=m;i++){
add(d[i].u ,d[i].v ,1,1);
}
for(int i=1;i<=n;i++){
if(st[i]){
int val=st[i]; //已知标号的点
if(val>>x&1) add(S,i,inf,0); //为1 连S
else add(i,T,inf,0); //为0 连T
}
}
return;
}
bool bfs(){}
ll dfs(int u, ll lim){}
ll dinic(int x){
build(x); //每次跑重新建图
ll ans=0;
while(bfs()) ans+=dfs(S,inf);
return ans;
}
int main(){
scanf("%d%d",&n,&m);
S=0;T=N-1;
for(int i=1;i<=m;i++){
scanf("%d%d",&d[i].u ,&d[i].v );
}
scanf("%d",&k);
for(int i=1;i<=k;i++){
int u,x;
scanf("%d%d",&u,&x);
st[u]=x;
}
ll ans=0;
for(int i=0;i<=31;i++){
ans+=dinic(i)<<i; //每一位的结果相加
}
printf("%lld",ans);
return 0;
}
网络战争
给出一个带权无向图 G=(V,E),每条边 e 有一个权 we。
求将点 s 和点 t 分开的一个边割集 C,使得该割集的平均边权最小,
即最小化: g = ∑ W e ∣ C ∣ g=\frac{\sum W_e}{|C|} g=∣C∣∑We
- 01分数规划 double二分
- 注意这里的割集与最小割的定义不一样。先要将所有权值为负的边算上(一定会使边权和减小),再在原图中删去这些边,只剩下大于0的边,再应用最小割。
设最优解
λ
=
∑
W
e
∣
C
∣
\lambda=\frac{\sum W_e}{|C|}
λ=∣C∣∑We
g
(
λ
)
=
∑
W
e
−
∣
C
∣
λ
g(\lambda)=\sum W_e-|C|\lambda
g(λ)=∑We−∣C∣λ
令
W
e
′
=
W
e
−
λ
W_e'=W_e-\lambda
We′=We−λ
g
(
λ
)
=
∑
W
e
′
g(\lambda)=\sum W_e'
g(λ)=∑We′
struct node{
int to,next,w;double f;
}e[M];
void add(int u,int v,int w){
e[++cnt].to =v; e[cnt].next=head[u]; e[cnt].w =w; head[u]=cnt;
e[++cnt].to =u; e[cnt].next=head[v]; e[cnt].w =w; head[v]=cnt;
}
bool bfs(){}
double dfs(int u,double lim){
double dinic(double mid){
double ans=0,res=0;
for(int i=2;i<=cnt;i+=2){
if(e[i].w-mid<=0){
res+=e[i].w-mid; //若权为负 则直接加入res
e[i].f=e[i^1].f=0; //在原图中删去
}
else e[i].f=e[i^1].f=e[i].w-mid; //赋新边权
}
while(bfs()) ans+=dfs(s,inf);
return ans+res;
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
double l=0,r=1e7;
while(r-l>eps){ //二分answer
double mid=(r+l)/2;
if(dinic(mid)<0) r=mid;
else l=mid;
}
printf("%.2lf",l);
}
最大权闭合图
最大获利
{ c ( u , v ) = ∞ c ( s , v ) = W v c ( v , t ) = ∣ W v ∣ \left\{ \begin{matrix} c(u,v)=\infty \\ c(s,v)=W_v \\ c(v,t)=|W_v| \end{matrix} \right. ⎩⎨⎧c(u,v)=∞c(s,v)=Wvc(v,t)=∣Wv∣
void add(int u,int v,int w){
e[++cnt].to =v; e[cnt].next=head[u]; e[cnt].w=w; head[u]=cnt;
e[++cnt].to =u; e[cnt].next=head[v]; e[cnt].w=0; head[v]=cnt;
}
bool bfs(){}
int dfs(int u,int lim){}
int dinic(){}
int main(){
scanf("%d%d",&n,&m);
S=0, T=N-3;
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
add(m+i,T,x);
}
int tot=0;
for(int i=1;i<=m;i++){
int a,b,x;
scanf("%d%d%d",&a,&b,&x);
tot+=x;
add(S,i,x);
add(i,m+a,inf);
add(i,m+b,inf);
}
printf("%d",tot-dinic());
}
最大密度子图
{ c ( u , v ) = 1 c ( s , v ) = U c ( v , t ) = U + 2 g − d v U = m \left\{ \begin{matrix} c(u,v)=1 \\ c(s,v)=U \\ c(v,t)=U+2g-d_v \\ \end{matrix} \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ U=m \right. ⎩⎨⎧c(u,v)=1c(s,v)=Uc(v,t)=U+2g−dv U=m
生活的艰辛
已知公司中一共有 n 名员工,员工之间共有 m 对两两矛盾关系。如果将一对有矛盾的员工安排在同一个团队,那么团队的管理难度就会增大。一个团队的管理难度系数等于团队中的矛盾关系对数除以团队总人数。团队的管理难度系数越大,团队就越难管理。约翰希望给儿子安排的团队的管理难度系数尽可能大。
void add(int u,int v,double w1,double w2){
e[++cnt].to=v; e[cnt].next =head[u]; e[cnt].w=w1; head[u]=cnt;
e[++cnt].to=u; e[cnt].next =head[v]; e[cnt].w=w2; head[v]=cnt;
}
void build(double k){
memset(head,0,sizeof head);
cnt=1;
for(int i=1;i<=m;i++){
add(d[i].x,d[i].y,1,1);
}
for(int i=1;i<=n;i++){
add(S,i,U,0);
add(i,T,2*k+U-du[i],0);
}
}
bool bfs(){}
double dfs(int u,double lim){}
double dinic(double k){}
void find(int u){
st[u]=1;
if(u!=S) ct++;
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to ;
if(e[i].w&&!st[v])
find(v);
}
}
int main(){
scanf("%d%d",&n,&m);
T=N-2; U=m;
for(int i=1;i<=m;i++){
scanf("%d%d",&d[i].x,&d[i].y);
du[d[i].x]++; du[d[i].y]++;
}
double l=0,r=m;
while(r-l>1e-4){
double mid=(r+l)/2;
double t=dinic(mid);
if(m*n-t>0) l=mid;
else r=mid;
}
dinic(l);
find(S);
if(!ct){
printf("1\n1"); return 0;
}
printf("%d\n",ct);
for(int i=1;i<=n;i++)
if(st[i]) printf("%d\n",i);
}
二分图之最小点权覆盖集
有向图破坏
爱丽丝和鲍勃正在玩以下游戏。首先,爱丽丝绘制一个 N 个点 M 条边的有向图。然后,鲍勃试图毁掉它。在每一步操作中,鲍勃都可以选取一个点,并将所有射入该点的边移除或者将所有从该点射出的边移除。已知,对于第 i 个点,将所有射入该点的边移除所需的花费为 W i + W^+_i Wi+,将所有从该点射出的边移除所需的花费为 W i − W^−_i Wi−。鲍勃需要将图中的所有边移除,并且还要使花费尽可能少。请帮助鲍勃计算最少花费。
void add(int u,int v,int w){
e[++cnt].to=v; e[cnt].next=head[u]; e[cnt].w =w; head[u]=cnt;
e[++cnt].to=u; e[cnt].next=head[v]; e[cnt].w =0; head[v]=cnt;
}
bool bfs(){}
int dfs(int u,int lim){}
int dinic(){}
void ddfs(int u){
st[u]=1;
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to ;
if(!st[v]&&e[i].w){
ddfs(v);
}
}
}
int main(){
scanf("%d%d",&n,&m);
T=N-1;
int x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
add(i+n,T,x);
}
for(int i=1;i<=n;i++){
scanf("%d",&x);
add(S,i,x);
}
int u,v;
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
add(u,v+n,inf);
}
int t=dinic();
printf("%d\n",t);
ddfs(S);
int ct=0;
for(int i=2;i<=cnt;i+=2){
if(!st[e[i].to]&&st[e[i^1].to]) ct++;
}
printf("%d\n",ct);
for(int i=2;i<=cnt;i+=2){
int a=e[i].to, b=e[i^1].to ;
if(!st[a]&&st[b]) {
if(a==T) printf("%d +\n",b-n);
else if(b==S) printf("%d -\n",a);
}
}
return 0;
}
二分图之最大点权独立集
王者之剑
给出一个 n×m 网格,每个格子上有一个价值 vi,j 的宝石。Amber 可以自己决定起点,开始时刻为第 0 秒。以下操作,在每秒内按顺序执行。
若第 i 秒开始时,Amber 在 (x,y),则 Amber 可以拿走 (x,y) 上的宝石。
在偶数秒时(i 为偶数),则 Amber 周围 4 格的宝石将会消失。
若第 i 秒开始时,Amber 在 (x,y),则在第 (i+1) 秒开始前,Amber 可以马上移动到相邻的格子 (x+1,y),(x−1,y),(x,y+1),(x,y−1) 或原地不动 (x,y)。
求 Amber 最多能得到多大总价值的宝石。
int get(int x,int y){ return (x-1)*m+y;}
void add(int u,int v,int w){
e[++cnt].to =v; e[cnt].next =head[u]; e[cnt].w=w; head[u]=cnt;
e[++cnt].to =u; e[cnt].next =head[v]; e[cnt].w=0; head[v]=cnt;
}
bool bfs(){}
int dfs(int u,int lim){}
int dinic(){}
int main(){
scanf("%d%d",&n,&m);
T=N-2;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int x; scanf("%d",&x);
tot+=x;
if(i+j&1){
add(S,get(i,j),x);
for(int k=0;k<4;k++){
int x=i+dx[k];
int y=j+dy[k];
if(x<=0||y<=0||x>n||y>m) continue; //边界
add(get(i,j),get(x,y),inf);
}
}
else add(get(i,j),T,x);
}
}
printf("%d",tot-dinic());
}
建图实战
有线电视网络
给定一张 n 个点 m 条边的无向图,求最少去掉多少个点,可以使图不连通。
如果不管去掉多少个点,都无法使原图不连通,则直接返回 n。
void add(int u,int v,int w){
e[++cnt].to =v; e[cnt].next =head[u]; e[cnt].w =w; head[u]=cnt;
e[++cnt].to =u; e[cnt].next =head[v]; e[cnt].w =0; head[v]=cnt;
}
bool bfs(){}
int dfs(int u,int lim){}
int dinic(){}
void ini(){
cnt=1; memset(e,0,sizeof e);
memset(head,0,sizeof head);
}
int main(){
T=N-2;
while(scanf("%d%d",&n,&m)!=EOF){
ini();
for(int i=0;i<n;i++){
add(i,i+n,1);
}
for(int i=1;i<=m;i++){
int x,y;
scanf(" (%d,%d)",&x,&y);
add(x+n,y,inf);
add(y+n,x,inf);
}
int tot=1e9;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
S=i+n ,T=j;
for(int l=2;l<=cnt;l+=2){
e[l].w+=e[l^1].w;
e[l^1].w=0;
}
tot=min(tot,dinic());
}
}
if(tot==1e9) printf("%d\n",n);
else printf("%d\n",tot);
}
}
太空飞行计划问题
配置仪器 Ik 的费用为 ck 美元。实验 Ej 的赞助商已同意为该实验结果支付 pj 美元。W 教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。
对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。
void add(int u,int v,int w){
e[++cnt].to =v; e[cnt].next =head[u]; e[cnt].w =w; head[u]=cnt;
e[++cnt].to =u; e[cnt].next =head[v]; e[cnt].w =0; head[v]=cnt;
}
bool bfs(){
queue<int>q;
memset(dep,0,sizeof dep);
q.push(S), dep[S]=1; now[S]=head[S];
while(!q.empty()){
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to;
if(!dep[v]&&e[i].w ){
dep[v]=dep[u]+1;
now[v]=head[v];
if(v==T) return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int u,int lim){
if(u==T) return lim;
int flow=0;
for(int i=now[u];i&&flow<lim;i=e[i].next ){
int v=e[i].to ;
int w=e[i].w;
now[u]=i;
if(dep[v]==dep[u]+1&&w){
int res=dfs(v,min(w,lim-flow));
e[i].w-=res;
e[i^1].w+=res;
flow+=res;
}
}
if(flow==0) dep[u]=0;
return flow;
}
int dinic(){
int ans=0;
while(bfs()) ans+=dfs(S,inf);
return ans;
}
void ddfs(int u){
st[u]=1;
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to;
if(!st[v]&&e[i].w) ddfs(v);
}
}
int main(){
m=read(); n=read();
T=N-2;
int tot=0;
for(int i=1;i<=m;i++){
string line;
getline(cin,line);
stringstream ssin(line); //黑科技读入
int w,id;
ssin>>w;
tot+=w;
add(S,i,w);
while(ssin>>id) add(i,id+m,inf);
}
for(int i=1;i<=n;i++){
int w=read(); add(i+m,T,w);
}
int res=dinic();
ddfs(S);
for(int i=1;i<=m;i++) if(st[i]) printf("%d ",i);printf("\n");
for(int i=m+1;i<=m+n;i++) if(st[i]) printf("%d ",i-m);printf("\n");
printf("%d",tot-res);
}
骑士共存问题
int get(int x,int y){ return (x-1)*n+y;}
void add(int u,int v,int w){
e[++cnt].to =v; e[cnt].next =head[u]; e[cnt].w =w; head[u]=cnt;
e[++cnt].to =u; e[cnt].next =head[v]; e[cnt].w =0; head[v]=cnt;
}
bool bfs(){}
int dfs(int u,int lim){}
int dinic(){}
int main(){
n=read(); m=read();
S=N-1, T=N-2;
for(int i=1;i<=m;i++){
int x=read(), y=read();
vis[x][y]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(vis[i][j]) continue;
if(i+j&1){
add(S,get(i,j),1);
for(int k=0;k<8;k++){
int x=i+dx[k], y=j+dy[k];
if(x<=0||y<=0||x>n||y>n||vis[x][y]) continue;
add(get(i,j),get(x,y),inf);
}
}
else add(get(i,j),T,1);
}
}
printf("%d",n*n-dinic()-m);
}