题目链接
题意就是给定一个无向图,每个点有权值
a
i
,
b
i
a_i,b_i
ai,bi,现在需要删去其中的一些点,其中删去一个点的花费为
a
i
a_i
ai,删点后的图的分数为每一个联通块的分数之和,一个联通块的分数是这个联通块的点的
b
i
b_i
bi的和的绝对值。要求删点后的图的分数减去删点的花费的最大值(
n
,
m
<
=
300
n,m<=300
n,m<=300)
很妙的最小割题
对于一个联通块,取绝对值之后就是这个联通块内的所有点的和乘上+1或者-1
这样我们就可以考虑给每一个没有删去的点赋上一个额外的值为+1或-1,其中+1和-1不能相邻因为一个联通块内的所有点的权值都是+1或者-1,然后我们要求其中的最小值。
事实上,我们可以先求所有的
b
i
b_i
bi的绝对值的和
s
u
m
sum
sum,这样对于点
i
i
i,删去它或者给这个点赋上+1或者-1的代价分别是:
如果
b
i
>
0
b_i>0
bi>0,则删点、赋为+1,-1的代价分别为:
a
i
+
b
i
,
0
,
2
b
i
a_i+b_i,0,2b_i
ai+bi,0,2bi
如果
b
i
<
0
b_i<0
bi<0,则删点、赋为+1,-1的代价分别为:
a
i
−
b
i
,
−
2
b
i
,
0
a_i-b_i,-2b_i,0
ai−bi,−2bi,0
仔细想想都比较容易得到,这里举
b
i
<
0
b_i<0
bi<0,赋值为+1的情况为例,原本我们已经求得了所有
b
i
b_i
bi的绝对值的和,相当于对此时的
b
i
b_i
bi取了权值为
−
1
-1
−1,而实际上我们应该取+1,所以我们的代价就是原本最初加进去的
∣
b
i
∣
|b_i|
∣bi∣和要取+1时的
−
b
i
-b_i
−bi,故为
−
2
b
i
-2b_i
−2bi
转化为这个问题之后其实可以用最小割来解决。我们可以这样连边:先拆点,然后每个点S到i连一条流量为这个点赋值+1的代价的边,i到i’连接一条流量为删去这个点的代价的边,i’到T连接一条流量为这个点赋值-1的代价的边。对于原本在图上的一条边(u,v),我们在u’到v和v’到u分别连接一条流量为inf的边。这样求最小割即可。
大概原理:
对于u到v这条边中,要么删去其中一个点,要么不能同时选择+1或者-1,对应着在图上割就可以了
代码:
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=610,M=2410;
int n,m,S,T,ans,tot=1,a[N],b[N],cur[N],head[N],to[M],nxt[M],val[M],dep[N];
void add_edge(int u,int v,int w){
nxt[++tot]=head[u];
to[tot]=v;
val[tot]=w;
head[u]=tot;
return;
}
void Add_edge(int u,int v,int w){
add_edge(u,v,w);
add_edge(v,u,0);
return;
}
bool bfs(){
queue<int>q;
q.push(S);
for(int i=1;i<=T;i++){
dep[i]=-1;
cur[i]=head[i];
}
dep[S]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];~i;i=nxt[i]){
int v=to[i];
if(!val[i]||~dep[v])
continue;
dep[v]=dep[u]+1;
q.push(v);
}
}
return ~dep[T];
}
int dfs(int u,int now){
if(u==T||!now)
return now;
int ret=0;
for(int &i=cur[u];~i;i=nxt[i]){
int v=to[i];
if(dep[v]!=dep[u]+1||!val[i])
continue;
int dist=dfs(v,min(now,val[i]));
if(dist){
val[i]-=dist;
now-=dist;
val[i^1]+=dist;
ret+=dist;
}
}
return ret;
}
void Dinic(){
while(bfs())
ans-=dfs(S,0x7f7f7f7f);
return;
}
int main(){
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
S=(n<<1|1),T=S+1;
for(int i=1;i<=n;i++)
scanf("%d",a+i);
for(int i=1;i<=n;i++){
scanf("%d",b+i);
if(!b[i])
Add_edge(i,i+n,a[i]);
if(b[i]<0){
ans-=b[i];
Add_edge(S,i,-b[i]*2);
Add_edge(i,i+n,a[i]-b[i]);
}
if(b[i]>0){
ans+=b[i];
Add_edge(i+n,T,b[i]*2);
Add_edge(i,i+n,a[i]+b[i]);
}
}
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
Add_edge(v+n,u,2e9);
Add_edge(u+n,v,2e9);
}
Dinic();
printf("%d\n",ans);
return 0;
}