[BZOJ1497] [NOI2006] 最大获利 - 网络流

1497: [NOI2006]最大获利

Time Limit: 5 Sec   Memory Limit: 64 MB
Submit: 3897   Solved: 1910
[ Submit][ Status][ Discuss]

Description

新的技术正冲击着手机通讯市场,对于各大运营商来说,这既是机遇,更是挑战。THU集团旗下的CS&T通讯公司在新一代通讯技术血战的前夜,需要做太多的准备工作,仅就站址选择一项,就需要完成前期市场研究、站址勘测、最优化等项目。在前期市场调查和站址勘测之后,公司得到了一共N个可以作为通讯信号中转站的地址,而由于这些地址的地理位置差异,在不同的地方建造通讯中转站需要投入的成本也是不一样的,所幸在前期调查之后这些都是已知数据:建立第i个通讯中转站需要的成本为Pi(1≤i≤N)。另外公司调查得出了所有期望中的用户群,一共M个。关于第i个用户群的信息概括为Ai, Bi和Ci:这些用户会使用中转站Ai和中转站Bi进行通讯,公司可以获益Ci。(1≤i≤M, 1≤Ai, Bi≤N) THU集团的CS&T公司可以有选择的建立一些中转站(投入成本),为一些用户提供服务并获得收益(获益之和)。那么如何选择最终建立的中转站才能让公司的净获利最大呢?(净获利 = 获益之和 - 投入成本之和)

Input

输入文件中第一行有两个正整数N和M 。第二行中有N个整数描述每一个通讯中转站的建立成本,依次为P1, P2, …, PN 。以下M行,第(i + 2)行的三个数Ai, Bi和Ci描述第i个用户群的信息。所有变量的含义可以参见题目描述。

Output

你的程序只要向输出文件输出一个整数,表示公司可以得到的最大净获利。

Sample Input

5 5
1 2 3 4 5
1 2 3
2 3 4
1 3 3
1 4 2
4 5 3

Sample Output

4

HINT

【样例说明】选择建立1、2、3号中转站,则需要投入成本6,获利为10,因此得到最大收益4。【评分方法】本题没有部分分,你的程序的输出只有和我们的答案完全一致才能获得满分,否则不得分。【数据规模和约定】 80%的数据中:N≤200,M≤1 000。 100%的数据中:N≤5 000,M≤50 000,0≤Ci≤100,0≤Pi≤100。

Source

[ Submit][ Status][ Discuss]

    首先膜拜一发……一开始根本没想到怎么构图,一看题目感觉就是费用流……然后就瞎脑补了一个5W个点带上下界费用流,然后发现就是单纯形也会TLE+MLE…… 所以像我这么弱的蒟蒻就去找了一发题解…好像就是简单的网络流?…

    好吧首先简略地讲一下构图:我们将每个人和与他有关的两个中转站用一条边连起来,设流量为无穷大。源点向每个中转站连一条边,流量就是建造中转站的费用。由每个人向汇连一条边,流量就是接收了这个用户的获得的利益,然后跑一次最大流,用总盈利-最大流即可。

    那么为什么这样构图是正确的呢? 首先我们考虑这里最大流的实际意义是什么。

    很显然地,我们能得到一个等式如下:

        盈利 = 总盈利sum - 不选择的人的盈利和wsum - 需要的中转站的费用和vsum

    那么,为了使盈利最大,由于总盈利不变,就要使(wsum+vsum)的和最小。

    回过头来我们再来看最大流。因为我们将人和中转站间的边的流量定为了INF,所以从 源S 流向 中转站 再流向 人 最后流向 汇T 的路程中,人和中转站之间的流量不会限制总流量。

    因此,整个网络流图最终的残余网络中,所有到汇的不满流的弧的流量都来自于满流的弧(否则最大流可以更大!)。而如果一条从源流出的弧满流,则说明要花费这个中转站的费用。为什么由源流出的这条弧a会满流呢?是因为它下面与它相关的弧能提供超过弧a的最大流量的流量空间!这就相当于是盈利>支出,选择修建这座中转站的情况。同样地,如果流入汇的弧b满流,则说明不管这个用户,因为它下面与它相关的弧不能负荷这么大的支出!最后我们可以得知,最大流 = min {wsum+vsum},因为显然不存在另一种分配方式更划算了。

    这里我们可以再换一个角度分析:首先假设得到的并不是最优解,那么假设我们在目前已经修建的中转站中,不修建中转站x。显然,不修建中转站x则会导致下面与x相关的人都无法被盈利,而这些人的总价值显然要大于费用,否则不满足最大流的性质。如果我们修建一个新的中转站y,那么就是在从源出的弧上令其满流。然而下面已经无法负荷更多的流量了(全部满流),就是在浪费金钱了,无法获得更多的回报。

    以上理论成立的原因,是因为拆掉源点S和汇点T之后,图会变成一个全部是无穷大流量的二分图。

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -分割线- - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    一开始我是用最高标号预流推进,也就是俗称的HLPP实现的。遗憾的是,由于点的数量过多,而HLPP的算法效率与上界相差不大。因此虽然HLPP拥有更低的理论复杂度,但由于大量的无效点的存在,最高标号预流推进算法在实现中,最后的两个点完美T飞。(当然也可能是我写的太丑的缘故)

    

    最高标号预流推进法代码如下(欢迎大家帮忙检查检查qwq):

#include "stdio.h"
#include "iostream"
#include "queue" 
#define inf 0x7fffffff
#define rep(f,a,b) for(f=a;f<=b;f++)
#define bk(x) x%2?x+1:x-1
using namespace std;
const int N=5005,M=50005;
int read(){
	int v=0; char ch=getchar();
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch<='9'&&ch>='0') { v=v*10+ch-'0'; ch=getchar();}
	return v;
}
int S,T,head[N+M],n,m,sum;
int tmp,a[N],b[M];
struct edge {
    int next,to,flow;
    void ins(int a,int b,int &c){
        to=a,flow=b;
        next=c; c=tmp;
    }
} e[8*M];
inline void add(int u,int v,int f){
    ++tmp; e[tmp].ins(v,f,head[u]);
    ++tmp; e[tmp].ins(u,0,head[v]);
}
struct data{
    int v,h;
    bool friend operator <(const data&a,const data&b){
        return a.h<b.h;
    }
} q;
priority_queue <data> que;
int ef[N+M],h[N+M],maxflow;
int MaxFlow(int s,int t){
    ef[s]=inf; ef[t]=-inf;
    q.v=s; h[s]=n+m+2; q.h=h[s];
    que.push(q); int u,v,i,mh;
    while (!que.empty()){
        q=que.top(); u=q.v; mh=inf; que.pop();
        for (i=head[u];i&&ef[u]>0;i=e[i].next) 
        {
            v=e[i].to;
            int p=min(e[i].flow,ef[u]);
            if(p>0&&h[u]!=h[v]+1) mh=min(mh,h[v]);
            if(p>0&&(u==s||h[u]==h[v]+1)){
                e[i].flow-=p; e[bk(i)].flow+=p;
                ef[u]-=p; ef[v]+=p;
                if(v==t) maxflow+=p;
                if(v!=s&&v!=t) {
                    q.v=v; q.h=h[v];
                    que.push(q);
                }
            }
        }
        if(u!=s&&u!=t&&ef[u]>0){
            h[u]=mh+1;
            q.h=h[u]; q.v=u;
            que.push(q);
        }
    }
    return maxflow;
}
int main(){
//  freopen("profit.in","r",stdin);
//  freopen("profit.out","w",stdout);
    n=read(),m=read(); int i,u,v; T=n+m+1;
    rep(i,1,n) a[i]=read(),add(S,i,a[i]);
    rep(i,1,m) u=read(),v=read(),b[i]=read(),add(u,i+n,inf),
               add(v,i+n,inf),add(i+n,T,b[i]),sum+=b[i];
    printf("%d\n",sum-MaxFlow(S,T));
    return 0;
}

    然后我就又到网上翻了翻,发现好多写sap-gap的啊 - - 不过我太弱,不会写sap+gap+当前弧优化,先贴sap+gap的运行时间(以在网上搜到的NanoApe[KPM]的程序为例):

    果然,时间与算法的上界相去甚远,程序的实际运行复杂度远低于理论的O(n^2 * m),比最高标号预流推进的理论O(n^2 * √m)要快了4000倍。

    于是我在想,既然无用的点如此繁多,为什么不能用Dinic来进行呢?果然,dinic的dfs+bfs的运行时间是sap-gap的二分之一左右。 链接:NOI2006 最大获利 测试数据下载 密码:5ml2

    

#include "stdio.h"
#include "iostream"
#define inf 0x7fffffff
#define rep(f,a,b) for(f=a;f<=b;f++)
#define travel(x,f) for(f=head[x];f;f=e[f].next)
#define bk(x) x%2?x+1:x-1
using namespace std;
const int N=5005,M=50005;
int read(){
	int v=0; char ch=getchar();
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch<='9'&&ch>='0') { v=v*10+ch-'0'; ch=getchar();}
	return v;
}
int S,T,head[N+M],n,m,sum;
int tmp,a[N],b[M];
struct edge {
    int next,to,flow;
    void ins(int a,int b,int &c){
        to=a,flow=b;
        next=c; c=tmp;
    }
} e[8*M];
inline void add(int u,int v,int f){
    ++tmp; e[tmp].ins(v,f,head[u]);
    ++tmp; e[tmp].ins(u,0,head[v]);
}
int maxflow,q[N+M],l,r,d[N+M],inq[N+M];
bool bfs(){
    l=1,r=0; q[++r]=S; int i,u,v;
    rep(i,S,T) d[i]=inf,inq[i]=0;
    d[S]=0; inq[S]=1;
    while(l<=r){ u=q[l++]; inq[u]=0;
        travel(u,i){ v=e[i].to;
            if (e[i].flow&&d[v]>d[u]+1){
                d[v]=d[u]+1;
                if(!inq[v]){
                    q[++r]=v;
                    inq[v]=1;
                }
            }
        }
    }
    return d[T]!=inf;
}
int dfs(int v,int mf){
    int i,cost=0,p,to;
    if(v==T) return mf;
    travel(v,i){
        if(mf==0) return cost; to=e[i].to;
        if(e[i].flow&&d[to]==d[v]+1){
            p=dfs(e[i].to,min(mf,e[i].flow));
            e[i].flow-=p; e[bk(i)].flow+=p;
            mf-=p; cost+=p;
        }
    }
    <strong>if(cost==0) d[v]=-1; //如果无法扩展,不再访问!</strong>
    return cost;
}
int dinic(){
    while (bfs()) 
        maxflow+=dfs(S,inf);
    return maxflow;
}
int main(){
//  freopen("profit.in","r",stdin);
//  freopen("profit.out","w",stdout);
    n=read(),m=read(); int i,u,v; T=n+m+1;
    rep(i,1,n) a[i]=read(),add(S,i,a[i]);
    rep(i,1,m) u=read(),v=read(),b[i]=read(),add(u,i+n,inf),
               add(v,i+n,inf),add(i+n,T,b[i]),sum+=b[i];
    printf("%d\n",sum-dinic());
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值