BZOJ 3438 浅谈DINIC及一点点优化卡时技巧

来源:互联网 发布:java数组删除指定元素 编辑:程序博客网 时间:2024/06/03 17:43

这里写图片描述
如tarjan一样,学了dinic也已经很久了,但还是一直模模糊糊,会打,能a,但一直不知其原理,这道题的构图方式着实不错,烧脑子,值得记叙。
description:

小M在MC里开辟了两块巨大的耕地A和B(你可以认为容量是无穷),现在,小P有n中作物的种子,每种作物的种子有1个(就是可以种一棵作物)(用1...n编号),现在,第i种作物种植在A中种植可以获得ai的收益,在B中种植可以获得bi的收益,而且,现在还有这么一种神奇的现象,就是某些作物共同种在一块耕地中可以获得额外的收益,小M找到了规则中共有m种作物组合,第i个组合中的作物共同种在A中可以获得c1i的额外收益,共同总在B中可以获得c2i的额外收益,所以,小M很快的算出了种植的最大收益,但是他想要考考你,你能回答他这个问题么?

input:

第一行包括一个整数n第二行包括n个整数,表示ai第三行包括n个整数,表示bi第四行包括一个整数m接下来m行,对于接下来的第i行:第一个整数ki,表示第i个作物组合中共有ki种作物,接下来两个整数c1i,c2i,接下来ki个整数,表示该组合中的作物编号。输出格式

output:

只有一行,包括一个整数,表示最大收益

这道题乍一看并不容易想到网络流,但记得某大神说过,不知怎么做的题就往网络流方面想,想着想着,就卡出来了。
突破口是一个种子只能种一块土地,换句话说就必须放弃种在另一块土地的价值,考虑每一种作物,不考虑集合buff,贪心的取较大价值的土地,就是放弃较小的土地,那这两条边并在一起的最大流即最小割必然是价值较小的那一块,那对每一个作物,分别向源点连一条流量为a[i]的边,向汇点连一条流量为b[i]的边,就解决了单个作物的价值问题,因为每一条路上的最大流就是较小边权的权值,总的最大流就是应该减去的边权和。
其实单单是这样根本用不到网络流,贪心就好,主要是还有集合buff的问题。
我们要知道,最大流即最小割就是我们想要放弃的边权和,将图分成互不相连得两部分,而对于任意一个点集,只有当全部的点去A或B时才能享受到集合buff,也就是说,最小割里少减了一个集合buff的值,而点集中的所有点都到A,就等于都放弃B,就是这些点通向B的边全部放弃,即全部取到最大流,全部断开,所以这些点在图中就不能再通过另外任何一条边到达源点或汇点,就是说集合buff的权值必须全部取到,那另一边的集合buff就要断开,即满流。而对于整个点集,从源点通过其前往汇点的路径上,必然是流量较小的方案(边集)满流,就是这条路径的最大流。
分析到这一步,对于集合点的构建方式就豁然明朗了。
每个收益i拆成两个点i1、i2,分别表示全部种在A和全部种在B
每个收益i2向对应种子连一条流量为inf的边(为了不影响决策)
源点S向每个收益i2连一条流量为c2i的边
对应种子向每个收益i1连一条流量为inf的边
每个收益i1向汇点T连一条流量为c1i的边
这样,如果点集各部分连向A的满流,部分连向B的满流,为了最大流,buff点与源点汇点的边必须满流,正好表示断开,舍弃buff的加成。
各种情况都被这张图概括,跑一遍dinic就行了。
但是
关于这个dinic,也不是那么简单,这道题有点卡时,稍不注意就要超时,在这里提供一点点卡时技巧(没有当前弧优化,要看当前弧优化点这里)
首先是不是那么6的,bfs时每次queue动态申请空间会炸,时间耗费太严重,不如提前申请好空间,每次清空。当然直接用数组的写法也是可以的,反正不要动态申请就行了,代码:

bool bfs(){    int h=0,t=1;    memset(dis,-1,sizeof(dis));    while(!state.empty()) state.pop();    state.push(S);    dis[S]=0;    while(!state.empty())    {        int u=state.front();        state.pop();        for(int i=head[u];i;i=ed[i].last)        {            int v=ed[i].v;            if(dis[v]==-1&&ed[i].w>0)            {                dis[v]=dis[u]+1;                state.push(v);            }        }    }    if(dis[T]==-1) return false;    return true;}

在dfs时,也有卡时技巧,代码:

int dfs(int u,int low){    int a=0;    if(u==T || low==0) return low; //流量为0说明无法增广,return    for(int i=head[u];i;i=ed[i].last)    {        int v=ed[i].v;        if(ed[i].w>0&&dis[v]==dis[u]+1)        {            int tmp=dfs(v,min(low,ed[i].w));            ed[i].w-=tmp,ed[i^1].w+=tmp;            a+=tmp;            low-=tmp;            if(low==0) return a; //流量为0就没有必要继续了        }    }    if(low) dis[u]=-1; //提前将u的层次图删去,剪枝以免再次访问    return a;}

完整代码如下:

#include<stdio.h>#include<algorithm>#include<queue>#include<cstring>#define INF 0x3f3f3f3fusing namespace std;struct edge{    int v,w,last;}ed[4100010];int num=1,head[100010],dis[100010],sum=0,a[10010],b[10010];int n,m,S,T,x,y,z,ans=0;queue<int> state;void add(int u,int v,int w){    num++;    ed[num].v=v;    ed[num].w=w;    ed[num].last=head[u];    head[u]=num;}bool bfs(){    int h=0,t=1;    memset(dis,-1,sizeof(dis));    while(!state.empty()) state.pop();    state.push(S);    dis[S]=0;    while(!state.empty())    {        int u=state.front();        state.pop();        for(int i=head[u];i;i=ed[i].last)        {            int v=ed[i].v;            if(dis[v]==-1&&ed[i].w>0)            {                dis[v]=dis[u]+1;                state.push(v);            }        }    }    if(dis[T]==-1) return false;    return true;}int dfs(int u,int low){    int a=0;    if(u==T || low==0) return low;    for(int i=head[u];i;i=ed[i].last)    {        int v=ed[i].v;        if(ed[i].w>0&&dis[v]==dis[u]+1)        {            int tmp=dfs(v,min(low,ed[i].w));            ed[i].w-=tmp,ed[i^1].w+=tmp;            a+=tmp;            low-=tmp;            if(low==0) return a;        }    }    if(low) dis[u]=-1;    return a;}int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];    for(int i=1;i<=n;i++) scanf("%d",&b[i]),sum+=b[i];    scanf("%d",&m);    S=0,T=n+2*m+1;    for(int i=1;i<=n;i++)    {        add(S,i,a[i]);        add(i,S,0);    }     for(int i=1;i<=n;i++)     {        add(i,T,b[i]);        add(T,i,0);    }    for(int i=1;i<=m;i++)    {        scanf("%d",&z);        scanf("%d%d",&x,&y);        add(S,n+i*2,x);        add(n+i*2,S,0);        add(n+i*2-1,T,y);        add(T,n+y*2-1,0);        sum+=x+y;        for(int j=1;j<=z;j++)        {            scanf("%d",&y);            add(n+i*2,y,INF);            add(y,n+i*2,0);            add(y,n+i*2-1,INF);            add(n+i*2-1,y,0);        }    }    while(bfs()) ans+=dfs(S,INF);    printf("%d",sum-ans);} 

嗯,就是这样

原创粉丝点击