题解 P3872 【[TJOI2010]电影迷】
YellowBean_Elsa · · 题解
S 向正权点建边,容量为点权;负权点向 T 建边,容量为点权绝对值,
x->y 的限制就直接建 x->y。最后拿正权和-最小割就是答案。
这是神仙出题人的方法,我来给一个完整的正确性证明。
证明
若一个限制条件连的 x, y 点权不同号,在取了其中正的那个后,要么放弃正的,要么取了负的,要么减少 dXY,与连边方式的最小割相同。
如果同号会怎样呢?
我们任意选两个同号点1, 2,连边2->1,选1的任意一条到T的路径1->3->T。(如果没有这条路径那么1,2常规都选(负点权则不选)答案不受影响)
边a, x, y中至少有一条删掉。
1. 删x或y:
1,2仍然都选,且不用割去其它边,答案不受影响。
2. 删a:
1不选了,这时要么不选2(割b)要么损失d12(割c)。
如果割x或y了咋办?
怎么可能,那样转化为上一种情况,a就不用割了,不是最小割。
那这样答案也不受影响了。
综上
同号的限制条件也解决了。
证毕
最后还是贴一下代码吧。
//coder: Feliks*GM-YB
#include<bits/stdc++.h>
#define fu(i,a,b) for(register int i = a, I = (b) + 1; i < I; ++i)
#define fd(i,a,b) for(register int i = a, I = (b) - 1; i > I; --i)
typedef long long ll;
using namespace std;
template <class T> inline void read(T &x) {
x=0;T f=1;char ch=getchar();
while(!isdigit(ch))f=ch=='-'?-1:1,ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
x*=f;
}int n,m;
#define go(x) for(int i=first[x],y=v[i];i;i=nex[i],y=v[i])
const int N=1e3+5;
const int M=1e5+5;
int v[M],nex[M],w[M],first[N],tot=1;
inline void add(int x,int y,int z){
v[++tot]=y;w[tot]=z;
nex[tot]=first[x];
first[x]=tot;
}namespace Dinic{
const int s=0;
const int t=N-4;
const int inf=1e9+7;
int d[N],flow,ans;
queue<int> q;
inline bool bfs(){
while(!q.empty())q.pop();
memset(d,0,sizeof(d));
q.push(s);d[s]=1;
while(!q.empty()){
int x=q.front();q.pop();
go(x){
if(!w[i] || d[y])continue;
q.push(y);
d[y]=d[x]+1;
if(y==t)return 1;
}
}return 0;
}int dinic(int x,int f){
if(x==t)return f;
int res=f,k;
for(int i=first[x],y=v[i];i && res;i=nex[i],y=v[i]){
if(!w[i] || d[y]!=d[x]+1)continue;
k=dinic(y,min(res,w[i]));
if(!k)d[y]=0;
w[i]-=k;w[i^1]+=k;
res-=k;
}return f-res;
}inline void solve(int c){
while(bfs()){
while(flow=dinic(s,inf))ans+=c*flow;
}
}
}using namespace Dinic;
int a[N],df;
int main(){
read(n),read(m);
fu(i,1,n){
read(a[i]);
if(a[i]>=0)add(s,i,a[i]),add(i,s,0),ans+=a[i];
else add(i,t,-a[i]),add(t,i,0);
}fu(i,1,m){
int x,y;
read(x),read(y);
read(df);
add(x,y,df),add(y,x,0);
}solve(-1);
printf("%d\n",ans);
return 0;
}