本篇博客介绍的算法是解决仙人掌最大权独立集问题的一种巧妙的方法。
在此强调,该算法为E.Space提出,我不是该算法的发明者。
简单环:集合 C={v1,e1,v2,e2,⋯,vl,el} C = { v 1 , e 1 , v 2 , e 2 , ⋯ , v l , e l } ,其中无向边 ei=(vi,v(imodl)+1) e i = ( v i , v ( i mod l ) + 1 ) ,且集合内两两元素互不相同。
仙人掌:每条边最多出现在一个简单环内的图。
图的独立集:对于图 G G 中两两互不相邻的节点构成的集合。
最大权独立集问题:对于图 G G , G G 中每个节点 i i 都有一个点权 ci c i 。求图的独立集使得权值和最大。
对于图的最大权独立集是NPC(Non-deterministic Polynomial complete problem)的。但是仙人掌最大权独立集是可以在多项式时间内完成的。熟知的有圆方树做法,而我们介绍的是另一种做法。
我们先简化问题:树上最大权独立集。
对于树上问题,我们可以用树形DP来解决。但我们可以考虑另一种做法:
对于树上节点 x,y x , y , y y 是叶子节点, x x 与 y y 有一条边相连。
我们如果不考虑独立集。取了 y y 之后就能直接取 x x ,显然是最大的。
但是我们有独立集的限制,取了 y y 之后不能取 x x 。
如果我们一定要取 x x ,必须删掉之前取的 y y 。
真的是这样吗?我们关心的真的是 y y 吗?
其实我们关心的是 y y 的点权 cy c y 对答案的贡献。
如果能在 x x 中扣掉选 y y 的贡献,那么此时选 x x 就相当于不选 y y 。可以理解为一个撤销操作。
对于叶子节点 y y ,
重复以上步骤即可。
回到仙人掌。仙人掌和树的区别是环。
那么我们试着用类似的方法,即删除点,改变一些权值,获得撤销的效果。
考虑点度为 2 2 的点。
我们考虑点 x x 分别连接 y,z y , z ,我们要删除点 x x 。
如果用同样的方法:删除 x x ,将 cy c y 和 cz c z 同时减 cx c x 。问题出现了:如果同时选了 y y 和 z z ,那么按上面的算法,对答案的贡献是 cx+cy−cx+cz−cx=cy+cz−cx c x + c y − c x + c z − c x = c y + c z − c x ,多减了一个 cx c x 。
怎么办呢?真的只能修改点权吗?
我们定义:当边 (x,y) ( x , y ) 两端的点 x,y x , y 同时被选中时,答案需要额外贡献这条边的边权。
考虑到边权的定义,显然的,重边可以直接权值相加来合并。
有了边权的辅助,我们可以在删除 x x 的时候,不仅将 cy c y 和 cz c z 同时减 cx c x ,还要在 y,z y , z 之间建立一条权值为 cx c x 的边。那么就可以达成要求了。
边权显然要赋初值为 −∞ − ∞ ,这意味着选相邻的两个点是不合法的。
如果连接的边权不是 −∞ − ∞ 呢?
考虑点 x x 分别连接 y,z y , z , (x,y),(x,z) ( x , y ) , ( x , z ) 的边权分别为 a,b a , b ,同样的删除点 x x 。
我们只需要考虑 cx,a,b c x , a , b 对答案的贡献。
按照之前的套路:删除节点,添加贡献,修改点权,添加边权。
这样就可以缩小问题规模了。
考虑点度为 1 1 的点。可以构造一个点权为 0 0 的点连接在 x x 上,边权为 0 0 。然后用同样的方法,点度为 1 1 的点就被消去了。显然,我们不必要建这个新的点。
而仙人掌中,一直消去点度不大于 2 2 的点,一定能消除整棵仙人掌。
以上就是求仙人掌最大权独立集的方法。该算法将问题不断缩小为一个个子问题,有效运用了拓扑序,而撤销的思想对我们也有很大的学习意义。
我们已经能得到仙人掌最大权独立集的答案,但对于具体的方案还没有明确说明。
我们还是先从树开始考虑。
我们发现,对于一个节点是否选取,和与它相连的节点有很大的关系。
我们考虑在删点的时候它影响的那个点。
其实我一直很希望叫这个点为父亲节点,只不过之前一直用无根树的讲法,所以一直没有使用。但一直这样写下去会遇到一些麻烦,所以我在此补充两点定义。
对于树上最后一个节点,我们默认它是根。
这样在删除一个点的时候,影响的就是它的父亲节点。
显然的,这样的补充定义对答案没有影响。
我们记 f[x] f [ x ] 为 x x 的父亲节点, g[x][0/1] g [ x ] [ 0 / 1 ] 为 x x 的父亲节点 f[x] f [ x ] 选 (1) ( 1 ) 或不选 (0) ( 0 ) 时, x x 选取的情况 (0/1) ( 0 / 1 ) 。
那么有
g[x][1]=0,g[x][0]=0/1 g [ x ] [ 1 ] = 0 , g [ x ] [ 0 ] = 0 / 1
如果 f[x] f [ x ] 选中, x x 显然不能选;但如果 f[x] f [ x ] 不选,为什么 x x 还是不一定选呢?
因为如果 x x 的权值是负数就没必要选它了。
记录 ans[x] a n s [ x ] 为 x x 在最优方案下的选取方案。
对于根,如果操作到最后时权值是正数,则取;否则,不取。
对于非根节点,则有
ans[x]=g[x][ans[f[x]]] a n s [ x ] = g [ x ] [ a n s [ f [ x ] ] ] ;
(以下两种写法都省略了对根的操作,省略号代表该操作)
可以记忆化搜索:
int ans[MAXN];
bool Dfs(int x){
if(ans[x]!=-1)return ans[x];
return ans[x]=g[x][Dfs(f[x])];
}
void make_plan(){
memset(ans,-1,sizeof(ans));
......
for(int i=1;i<=n;i++)
if(ans[i]==-1)
Dfs(i);
return;
}
也可以按照逆拓扑序:
int topo[MAXN];
int ans[MAXN];
bool Dfs(int x){
if(ans[x]!=-1)return ans[x];
return ans[x]=g[x][Dfs(f[x])];
}
void make_plan(){
memset(ans,-1,sizeof(ans));
std::reverse(topo+1,topo+n+1);
......
for(int i=2;i<=n;i++)
ans[i]=g[i][ans[f[i]]];
return;
}
不要忘记,我们真正的问题还是仙人掌。
同样的,仙人掌经过操作之后还是会有一个点度为 0 0 的点留下来,类似于之前的根。
对于点度为 0 0 的点我们还是可以直接判断是否选取。
我们记 f[x][0/1] f [ x ] [ 0 / 1 ] 为要删除点 x x 时它连接的两个点分别是 f[x][0],f[x][1] f [ x ] [ 0 ] , f [ x ] [ 1 ] 。记 g[x][0/1/2/3] g [ x ] [ 0 / 1 / 2 / 3 ] 为 x x 所连接的两个点的选取情况(有四种情况,见 2.2)导致 x x 所对应的选取情况 (0,1) ( 0 , 1 ) 。
后面就和树有些类似了,可以自己推导一下。
以上,我介绍了这种求解仙人掌最大权独立集的奇妙方法。虽然圆方树能求得答案,但对具体方案的求解就相对复杂一点。本文介绍的这种算法就有它一定的优势了。
(如果对于3.3的结尾仍然有疑惑的朋友,可以回复我,可能之后会有所补充。)
再次强调,该算法由我的学长E.Space提出,我只是进行了简单的整理和总结。再次感谢E.Space。