[HDU 5263] 平衡大师 题解

[HDU 5263] 平衡大师

这道题是一道上下界网络流最小费用类的题目

这道题十分精妙。

首先,最简单的一步,我们需要发现问题实际上就是删边来最小化每个点出度-入度 $dif_i$ 的最大值

由于这是一道网络流,我们不妨思考一下流量的意义

我们发现,因为是删边,所以我们可以把流经过的边表示成已经被删除的边

这时,我们发现我们需要一种方式来修改 $dif_i$

First,我们当然要建超级源汇点 $S$ 和 $T$ 。

然后,我们神奇地发现,若我们 $s$ 向点 $u$ 连一条边,若流量为 $x$ ,且这是合法的,那么这些流量只能向这条边的出边流动。那么向出边流动代表着什么呢?由于每条边的流量限制当然是上界为1,所以这代表着会有 $x$ 条 $u$ 的出边会被删除,所以根据 $dif_u = 出度-入度$ , $dif_u$ 会减少 $x$ 。

同样地,若将一个点 $u$ 与点 $t$ 连条边,若流量为 $x$,且是合法可被满足的,那么必须至少有 $x$ 条入边向 $u$ 这个点流了流量,也就是说会有 $x$ 条入边被删除了。所以这时,$dif_u$ 会增加 $x$。

然后,根据合法性,我们便可以分讨来确定 $x$ 应在哪个范围

当然,在原图 $u->v$ 边之间需要有1的费用

于是,我们便可以二分这个答案

具体地,引用一下分讨过程:

v为当前二分的答案

1.若dif[u]>=v:

则dif[u]要减去一个数x(x>=0),

因为-v<=dif[u]-x<=v,

所以dif[u]-v<=x<=dif[u]+v,

从s到u连一条上界为dif[u]+v,下界为dif[u]-v,费用为0的边

2.若-v$<$dif[u]$<$v:

从s到u连一条上界为dif[u]+v,费用为0的边

从u到t连一条上界为v-dif[u],费用为0的边

3.若dif[u]<=-v:

从u到t连一条上界为v-dif[u],下界为-v-dif[u],费用为0的边

于是,就做完了

这道题的精髓在于将操作问题转化为网络流上的流动问题,也就是为每一个流量,每一条连边赋予与题目要求相关的实际意义

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include<bits/stdc++.h>

using namespace std;
// 网络流全局变量:s,t为上下界流超级源汇;s1,t1原图虚拟源汇;k题目允许删边数量;cnt边计数;n,m点数边数
// head邻接表头;dis最短路距离;pid记录到达每个点的前驱边;visSPFA标记数组
int s,t,s1,t1,k,cnt,n,m,head[1010],dis[1010],pid[1010],vis[1010];
// 网络流边结构体:from起点 to终点 nxt下一条邻边 c剩余容量 w单位费用
struct no{
int from,to,nxt,c,w;
}e[200010];
// 加边函数:正向边u->v容量c费用w,反向边v->u容量0费用-w
void add(int u,int v,int c,int w){
e[++cnt]={u,v,head[u],c,w};
head[u]=cnt;
e[++cnt]={v,u,head[v],0,-w};
head[v]=cnt;
}
// SPFA求最小费用最短路,返回是否存在从s到t的可行路
int SPFA(){
memset(dis,0x3f,sizeof dis); // 距离初始化为无穷大
memset(pid,0,sizeof pid); // 前驱边清零
memset(vis,0,sizeof vis); // 入队标记清零
queue<int>q;
q.push(s);
dis[s]=0;
vis[s]=1;
while(q.size()){
int u=q.front();
q.pop();
vis[u]=0; // 出队取消标记
// 遍历u所有邻边
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
// 边有剩余容量 且可以松弛更短费用路径
if(e[i].c>0 and dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
pid[v]=i; // 记录v是通过i号边过来的
if(!vis[v])q.push(v),vis[v]=1;
}
}
}
return dis[t]!=0x3f3f3f3f; // 终点距离不是无穷说明有路
}
// EK最小费用流,返回pair{总费用,总流量}
pair<int,int>EK(){
int flow=0,cost=0;
while(SPFA()){ // 不断找最短路增广
int v=t,f=0x3f3f3f3f;
// 找这条增广路上最小剩余容量(可增广流量)
while(pid[v]){f=min(f,e[pid[v]].c);
v=e[pid[v]].from;
}
v=t;
// 沿增广路更新容量、累加费用
while(pid[v]){cost+=f*e[pid[v]].w;
e[pid[v]].c-=f; // 正向边减去流量
e[pid[v]^1].c+=f; // 反向边增加流量
v=e[pid[v]].from;

}
flow+=f; // 总流量累加本次增广量
//cost+=f*dis[t];
}
return {cost,flow};
}
int dif[2010],aa[2010],bb[2010],sum[2010];
// 二分答案check函数:当前判断最大值v是否合法
int check(int v){
cnt=1; // 重置边计数器,重建图
memset(head,0,sizeof head); // 邻接表清空
memset(sum,0,sizeof sum); // 上下界平衡量数组清零
s1=n+1,t1=s1+1,s=t1+1,t=s+1; // 分配原图虚拟源s1、虚拟汇t1,上下界超级源s、超级汇t
add(t1,s1,0x3f3f3f3f,0); // s1-t1环,用于原图无汇源的无源汇流转换
// 建原图每条边u->v,容量1费用1,代表删这条边花费1代价
for(int i=1;i<=m;i++)add(aa[i],bb[i],1,1);

// 根据每个点dif[i]分三类建s1/t1与点的约束边,维护度数差限制
for(int i=1;i<=n;i++){
if(dif[i]>v){ // 情况1:原始度数差超过v,需要流出x降低差值
add(s1,i,2*v,0);
sum[s1]-=dif[i]-v; // 平衡量更新:s1需要流出dif[i]-v
sum[i]+=dif[i]-v;
}else if(dif[i]>-v){ // 情况2:度数差在[-v,v]区间内,可增可减
add(s1,i,dif[i]+v,0);
add(i,t1,v-dif[i],0);
}else{ // 情况3:原始度数差小于-v,需要流入x抬高差值
add(i,t1,2*v,0);
sum[i]-=-v-dif[i];
sum[t1]+=-v-dif[i];
}
}
int pd=0; // 记录总需要流出的流量,用于判断是否满流
// 上下界网络流标准建图:平衡量>0连超级源s,<0连超级汇t
for(int i=1;i<=t1;i++){
if(sum[i]>0)add(s,i,sum[i],0),pd+=sum[i]; // 该点需要流入sum[i]流量
if(sum[i]<0)add(i,t,-sum[i],0); // 该点需要流出-sum[i]流量
}
auto xx=EK(); // 跑最小费用流
// 满流说明约束全部满足,且总删边代价<=允许数量则v合法
if(xx.second==pd)return xx.first<=k;
return 0;
}
int bh=0; // 字符串映射点编号计数器
map<string,int>mp; // 字符串名称映射到数字点号
void solve(){
cin>>n>>m>>k;
bh=0;
memset(dif,0,sizeof dif); // dif[i] = 出度-入度 清零
k=m-k; // 最多保留k条边 = 最少删除m-k条边
mp.clear();
// 读入m条边,字符串映射点,统计每个点出度入度差值dif
for(int i=1;i<=m;i++){
string a,b;
cin>>a>>b;
if(!mp[a])mp[a]=++bh;
if(!mp[b])mp[b]=++bh;
aa[i]=mp[a];
bb[i]=mp[b];
dif[aa[i]]++; // a是起点,出度+1
dif[bb[i]]--; // b是终点,入度+1
}
// 二分答案,最小化所有点dif绝对值的最大值
int l=0,r=n,ans;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))ans=mid,r=mid-1; // mid可行,尝试更小值
else l=mid+1; // mid不可行,放大限制
}
cout<<ans<<'\n';
}
main(){
int tt,id=0;
cin>>tt;
while(tt--)id++,cout<<"Case #"<<id<<":\n",solve();
return 0;
}

[HDU 5263] 平衡大师 题解
https://zxtweb.fun/posts/cnb-21145255/
作者
站长
发布于
2026年7月5日
许可协议