-
参考资料:
-
定义:有一个有向图,每一个点都有一个权值(可以为正或负或0),选择一个权值和最大的子图,使得每个点的后继都在子图里面,这个子图就叫最大权闭合子图。
-
求解问题:选一部分这点,选了这部分点必须要选另一些点,求怎么选从权值最大。
-
建图方法:源点s向所有正权点连容量为点权的边,所有点权为负的边向汇点t连容量为点权绝对值的边,按照原图连容量为inf的边。
-
答案:最大权闭合子图的权值为正权值和-最小割。
-
个人理解:最大权闭合子图的权值和 = max{被选择的点权和} = 正点权和−min{没被选择的正权点之和 + 被选择的负权点绝对值和} = 正点权和−最小割;即考虑min{没被选择的正权点之和 + 被选择的负权点绝对值和} = 正点权和;
-
从割的角度看
由于原图的边都是无穷大,那么割边一定是与源点s或汇点t相连的。
割掉s与i的边,表示不选择i点作为子图的点;(当成前提条件理解,可推下面)
割掉i与t的边,表示选择i点为子图的点。(因为是最小割,割掉i与t的边意味着一定存在至少一个i的前驱点于s的边未被割)
如果s与i有边,表示i存在子图中;
如果i与t有边,表示i不存在于子图中。
- 根据割的意义得到合法性
1.因为割s->i意味着不选,割j->t意味着选,割不同属性的边含义不同,所以s与t联通意味着存在没选已选点的后继点的情况,所以要求割破坏图的连通性。
2.如果s与t不连通,选择了正权点i,一定选择了i后继中的所有负权点。设j是i的后继中的正权点,则割掉s到j的边是没有意义的,最小割不会割掉它,则j一点被选中,所以i的所有后继都被选中,符合闭合图的定义。
3(最优性).根据在本问题中割的含义(没选的正权点权值和和与选了的负权点的绝对值权值和),所有正权点的权值和减去这个值即为一个方案,若减的是最小割即为最大权闭合子图的点权和。
- 方案统计:对于正权点,割完之后依旧联通的点为方案;对于负权点,割掉的边所连的点为选择的点,在网络流图中即为t->j联通的点。具体实现通过dinic里用到的记录分层情况的数组来判断,看看是否有分层,因为如果正权点有分层,那么他们的后继点一定有分层(之间边的容量inf)
- 太空飞行计划问题
#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int inf=0x7f7f7f7f;
int n,m,cnt=1,last[2505],cur[2505],ceng[2505],ans,st=150,flow;;
struct edge{
int v,f,w,next;
}e[40005];
inline void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].next=last[u];
last[u]=cnt;
}
bool bfs()
{
queue<int>q;
for(int i=1;i<=st;i++)
ceng[i]=0;
ceng[0]=1;
q.push(0);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=last[u];i;i=e[i].next)
{
int v=e[i].v,w=e[i].w;
if(!ceng[v]&&w)
{
ceng[v]=ceng[u]+1;
q.push(v);
}
}
}
return ceng[st];
}
int dfs(int u,int dis)
{
if(u==st||!dis)
return dis;
for(int i=cur[u];i;i=e[i].next)
{
cur[u]=e[i].next;
int v=e[i].v,w=e[i].w;
if(ceng[v]==ceng[u]+1&&w)
{
int di=dfs(v,min(dis,w));
if(di>0)
{
e[i].w-=di;
e[i^1].w+=di;
return di;
}
}
}
return 0;
}
void dinic()
{
while(bfs())
{
for(int i=0;i<=st;i++)
cur[i]=last[i];
while(int d=dfs(0,inf))
flow+=d;
}
}
void getn(int u)
{
int w;
scanf("%d",&w);
char tools[10000];
memset(tools,0,sizeof tools);
cin.getline(tools,10000);
int ulen=0,tool;
ans+=w;
add(0,u,w);
add(u,0,0);
while (sscanf(tools+ulen,"%d",&tool)==1)
{
add(u,tool+m,inf);
add(tool+m,u,0);
if (tool==0)
ulen++;
else {
while (tool) {
tool/=10;
ulen++;
}
}
ulen++;
}
}
int main()
{
cin>>m>>n;
for(int i=1;i<=m;i++)
getn(i);
for(int i=1,w;i<=n;i++)
{
scanf("%d",&w);
add(i+m,st,w);
add(st,i+m,0);
}
dinic();
for(int i=last[0];i;i=e[i].next)
if(ceng[e[i].v])
cout<<e[i].v<<' ';
cout<<endl;
for(int i=last[st];i;i=e[i].next)
if(ceng[e[i].v])
cout<<e[i].v-m<<' ';
cout<<endl;
cout<<ans-flow;
return 0;
}