UOJ Logo zhouzixuan的博客

博客

bzoj 2163: 复杂的大门

2016-03-18 17:18:26 By zhouzixuan
【题目描述】:
    你去找某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;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。