【题目描述】:
你去找某bm玩,到了门口才发现要打开他家的大门不是一件容易的事……
他家的大门外有n个站台,用1到n的正整数编号。你需要对每个站台访问一定次数以后大门才能开启。站台之间有m个单向的传送门,通过传送门到达另一个站台不需要花费任何代价。而如果不通过传送门,你就需要乘坐公共汽车,并花费1单位的钱。值得庆幸的是,任意两个站台之间都有公共汽车直达。
现在给你每个站台必须访问的次数Fi,对于站台i,你必须恰好访问Fi次(不能超过)。
我们用u、v、w三个参数描述一个传送门,表示从站台u到站台v有一个最多可以使用w次的传送门(不一定要使用w次)。值得注意的是,对于任意一对传送门(u1,v1)和(u2,v2),如果有u1<u2,则有v1≤v2;如果有v1<v2,则有u1≤u2;且u1=u2和v1=v2不同时成立。
你可以从任意的站台开始,从任意的站台结束。出发去开始的站台需要花费1单位的钱。你需要求出打开大门最少需要花费多少单位的钱。
【题目解法】:
看到题目我们可以直接暴力费用流:
首先原点S 汇点T
S拆点S1 S2,连边(S1,S2,1,1)
S2向每个点连边(S2,i,INF,0)
每个点向汇点连边 (i,T,Fi,0)
然后对于每个传送门连边(x,y,w,0)
然后每两个点连边(x,y,INF,1)
然后从S1到T跑一遍最小费用最大流
然后我们发现边的级别是O(N^2)的TAT
考虑每个点必须经过Fi次,那么也就是说它的度数就是Fi*2
考虑一条带费用的遍可以对两个点各贡献一个度数
那么总的费用就是sigma(Fi)
然后使用一次传送门可以减少一条边
那么答案就是sigma(Fi)-传送门使用的最多次数
考虑如果求出最多的次数
相当于是对于每个点利用传送门找匹配
每找到一个匹配,那么我们就可以把他们原本需要花的费用省掉
想到于我们需要找到最多的匹配
我们把每个点拆成两个点i i'分别表示入点和出点
显然每个点进入的边和出去的边数都不能超过Fi次
连边(S,i,Fi) (i',T,Fi)
对于传送门连边(x,y',w)
然后最大流即可
复杂度O(Maxflow)
//Love LBL
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=20005;
const int M=800005;
const int INF=32083208;
struct edge {int x,y,op,next;} b[M];
int n,m,tot,a[N],ans,dis[N],pre[N],gap[N],cur[N],s,t,S,F[N];
inline long long getint()
{
long long x=0; char c=getchar(); bool flag=false;
while ((c!='-')&&((c<'0')||(c>'9'))) c=getchar();
if (c=='-') flag=true,c=getchar();
while ((c>='0')&&(c<='9')) x=x*10+(long long)(c-'0'),c=getchar();
if (flag) return -x; else return x;
}
inline void addedge(int x,int y,int z)
{
++tot; b[tot].x=y; b[tot].y=z; b[tot].op=tot+1; b[tot].next=a[x]; a[x]=tot;
++tot; b[tot].x=x; b[tot].y=0; b[tot].op=tot-1; b[tot].next=a[y]; a[y]=tot;
}
void init()
{
n=getint(); m=getint(); s=0; t=n+n+1;
for (int i=1; i<=n; i++) F[i]=getint(),ans+=F[i];
for (int i=1; i<=n; i++) addedge(s,i,F[i]),addedge(i+n,t,F[i]);
for (int i=1; i<=m; i++)
{
int x=getint(),y=getint(),z=getint();
addedge(x,y+n,z);
}
}
inline void augment()
{
int flow=INF;
for (int i=s; i!=t; i=b[cur[i]].x) flow=min(flow,b[cur[i]].y);
for (int i=s; i!=t; i=b[cur[i]].x) b[cur[i]].y-=flow,b[b[cur[i]].op].y+=flow;
ans-=flow;
}
void Sap()
{
for (int i=s; i<=t; i++) cur[i]=a[i]; gap[t]=t;
for (int now=s; dis[s]<=t;)
{
if (now==t) augment(),now=s; bool flag=false;
for (int p=cur[now];p;p=b[p].next)
{
int pp=b[p].x; if ((b[p].y<=0)||(dis[pp]+1!=dis[now])) continue;
flag=true; pre[pp]=now; cur[now]=p; now=pp; break;
}
if (flag) continue; if (--gap[dis[now]]==0) break; dis[now]=t+2; cur[now]=a[now];
for (int p=a[now];p;p=b[p].next) if (b[p].y>0) dis[now]=min(dis[now],dis[b[p].x]+1);
gap[dis[now]]++; if (now!=s) now=pre[now];
}
printf("%d\n",ans);
}
int main()
{
init();
Sap();
return 0;
}