如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
#include
#include
#include
#define INF 0x3f3f3f3f
using 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);
}
嗯,就是这样