hdu4694
题意:给定源点,求出源点到其他各点的关键点
Lengauer-Tarjan algorithm按理说这也是个经典算法,跟lca的tarjan和强连通的tarjan都有极其相似之处,但是貌似并没有推广
感觉出题比较好出,先求个dominator tree,然后再在上面各种搞,虽然多半会出成一个拼接题...
首先有几个链接
一些概念
http://en.wikipedia.org/wiki/Dominator_(graph_theory)#Algorithms
http://help.eclipse.org/juno/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fdominatortree.html
详细图解以及算法流程
http://www.cl.cam.ac.uk/~mr10/lengtarj.pdf
最后是中文详解
http://www.chnxp.com.cn/soft/down-17744.html 有一节是专门解释这个算法的
首先定义几个概念
idom 最近必经点 代表的是距离节点i最近的一个必经点,明显idom是唯一存在的dominator tree 这棵树上的每一个节点都是其儿子的idom
明显我们求出这棵树之后,原题要求的就是每个节点到根的路径上的各节点的编号和
然后辅助定义
semi 半必经点,表示的是在dfs树上,节点i的祖先中,可以通过一系列的非树边走到i的,深度最小的祖先,i的直系父亲也可以是半必经点
考虑节点i的前驱j
1、如果dfn[j]<dfn[i],则j明显是i的一个祖先,那么j是semi[i]的一个候选之一
2、dfn[j]>dfn[i],那么j的祖先u(包括j),semi[u]都是semi[i]的候选
在这二者中取dfn最小的,就是semi[i]
根据semi,我们可以求出idom
必经点定理
在semi[i]~i的一条链上(包括i,不包括semi[i]),找到一个节点y,使得dfn[semi[y]]最小,那么如果
1、semi[y]==semi[i],则idom[i]=semi[i];
2、semi[y]!=semi[i],则idom[i]=idom[y];
有了这两个定理,我们不难得出一个o(n^2)的暴力算法,但是Lengauer-Tarjan却给出了o(nlogn)的算法
我没有仔细去看这个算法的伪代码,大致看了一下这个算法的思想,感觉还是运用了并查集,并且有求lca的既视感,当然其运用到dfn的特征,又无疑是来自求强连通分量的
按照dfn从大到小的顺序处理每个节点,可以根据半必经点定理可以求出semi,这里我有点疑惑的是,此时dfn小于i的节点的semi还没求出来会不会产生影响,因为我发现实现的时候貌似是忽略了dfn比当前节点小的节点;其中有一步是查询其祖先的最小semi,这个可以用一个并查集实现,用best表示当前节点向上找到的dfn[semi[]]最小的节点,我们考虑每个子树都是儿子比祖先先处理,那么每次处理完一个节点的时候,把其子树都并起来,查询的时候通过路径压缩就可以快速查询了;然后是利用必经点定理,我们把semi[j]==i的节点j都链在i上,都处理完i的时候,i~j之间的semi都处理完了(是不是像lca),此时就可以开始求dfn[semi[y]]最小的y了,这个也可以用刚才的并查集实现,注意此时并不能直接求出idom,因为y的idom可能还没求出,要等到最后再按dfn从小到大扫一遍才可以求出来
我的实现与链接给出的伪代码应该有一些差别,但思想应该差不多
关于忽略dfn比当前小的节点,在主代码手的提示下,感觉是因为两点的lca之上的节点的semi都是不需要考虑的
updata2014.4.7 原创题一道 http://acm.sjtu.edu.cn/OnlineJudge/problem/1251
updata2014.4.9 这个拓展问题也比较有趣 http://cstheory.stackexchange.com/questions/17509/multiple-sources-dominator-trees-compact-representation-and-fast-algorithm
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
const int oo=1073741819;
using namespace std;
int tail[4][200000];
int next[4][2000000],sora[4][2000000];
int ss[4],top,w_time,n,m;
int rel[200000],semi[200000],b[200000],idom[200000],best[200000],st[200000],pre[200000];
int ans[200000];
void origin()
{
for (int e=0;e<=3;e++) ss[e]=n;
for (int i=1;i<=n;i++) {
for (int e=0;e<=3;e++)
tail[e][i]=i,next[e][i]=0;
rel[i]=0;
semi[i]=idom[i]=pre[i]=0,best[i]=i;
b[i]=i;
ans[i]=0;
}
rel[0]=oo;
}
void link(int e,int x,int y)
{
++ss[e],next[e][tail[e][x]]=ss[e],tail[e][x]=ss[e],sora[e][ss[e]]=y,next[e][ss[e]]=0;
}
void dfs(int x,int y)
{
++w_time,rel[x]=w_time;
st[++top]=x,pre[x]=y;
for (int i=x,ne;next[0][i];) {
i=next[0][i],ne=sora[0][i];
if (!rel[ne]) dfs(ne,x);
}
}
int find(int x)
{
int y=b[x];
if (b[x]!=x) b[x]=find(b[x]);
if (rel[semi[best[y]]]<rel[semi[best[x]]])
best[x]=best[y];
return b[x];
}
void getans(int x,int sum)
{
ans[x]=sum+x;
for (int i=x,ne;next[3][i];) {
i=next[3][i],ne=sora[3][i];
getans(ne,sum+x);
}
}
int main()
{
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
for (;scanf("%d%d",&n,&m)==2;) {
origin();
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
link(0,x,y);
link(1,y,x);
}
w_time=0;
top=0;
dfs(n,0);
for (int i=top;i>=1;i--) {
int ne=st[i];
for (int j=ne,na;next[1][j];) {
j=next[1][j],na=sora[1][j];
if (!rel[na]) continue;
if (rel[na]>rel[ne]) {
find(na);
int y=semi[best[na]];
if (rel[y]<rel[semi[ne]]) semi[ne]=y;
}
else {
int y=na;
if (rel[y]<rel[semi[ne]]) semi[ne]=y;
}
}
if (ne!=n) link(2,semi[ne],ne);
for (int j=ne,na;next[2][j];) {
j=next[2][j],na=sora[2][j];
find(na);
int y=best[na];
if (semi[y]==semi[na]) idom[na]=semi[na];
else idom[na]=y;
}
for (int j=ne,na;next[0][j];) {
j=next[0][j],na=sora[0][j];
if (pre[na]==ne) {
na=find(na);
b[na]=ne;
}
}
}
for (int i=2;i<=top;i++) {
int ne=st[i];
if (idom[ne]!=semi[ne]) idom[ne]=idom[idom[ne]];
link(3,idom[ne],ne);
}
getans(n,0);
for (int i=1;i<=n-1;i++) printf("%d ",ans[i]);
printf("%d\n",ans[n]);
//for (int i=1;i<=n;i++) cout<<semi[i]<<' ';cout<<endl;
}
return 0;
}
spoj59
求有向图的割点
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
const int oo=1073741819;
using namespace std;
int ss[3],tail[3][5050],next[3][210000],sora[3][210000];
int rel[5050],semi[5050],idom[5050],cnt[5050],pre[5050];
int w_time,b[5050],best[5050],st[5050];
int top,n,m;
void origin()
{
for (int e=0;e<=2;e++) ss[e]=n;
for (int i=1;i<=n;i++) {
for (int e=0;e<=2;e++) tail[e][i]=i,next[e][i]=0;
rel[i]=semi[i]=idom[i]=cnt[i]=0;
b[i]=best[i]=i;
}
}
void link(int e,int x,int y)
{
++ss[e],next[e][tail[e][x]]=ss[e],tail[e][x]=ss[e],sora[e][ss[e]]=y,next[e][ss[e]]=0;
}
void dfs(int x,int y)
{
++w_time,rel[x]=w_time;
st[++top]=x,pre[x]=y;
for (int i=x,ne;next[0][i];) {
i=next[0][i],ne=sora[0][i];
if (!rel[ne]) dfs(ne,x);
}
}
int find(int x)
{
int y=b[x];
if (b[x]!=x) b[x]=find(b[x]);
if (rel[semi[best[y]]]<rel[semi[best[x]]])
best[x]=best[y];
return b[x];
}
int main()
{
freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
for (;scanf("%d%d",&n,&m)==2;) {
origin();
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
link(0,x,y);
link(1,y,x);
}
w_time=top=0;
dfs(1,0);
rel[0]=oo;
for (int i=top;i>=1;i--) {
int ne=st[i],na;
for (int i=ne;next[1][i];) {
i=next[1][i],na=sora[1][i];
if (!rel[na]) continue;
if (rel[na]>rel[ne]) find(na),na=semi[best[na]];
if (rel[na]<rel[semi[ne]]) semi[ne]=na;
}
if (ne!=1) link(2,semi[ne],ne);
for (int i=ne;next[2][i];) {
i=next[2][i],na=sora[2][i];
find(na);
if (semi[best[na]]==ne) idom[na]=ne;
else idom[na]=best[na];
}
for (int i=ne;next[0][i];) {
i=next[0][i],na=sora[0][i];
if (pre[na]==ne) b[find(na)]=ne;
}
}
for (int i=2;i<=top;i++) {
int ne=st[i];
if (idom[ne]!=semi[ne]) idom[ne]=idom[idom[ne]];
cnt[idom[ne]]++;
}
int ans=0;
for (int i=1;i<=n;i++)
if (cnt[i]) ans++;
printf("%d\n",ans);
for (int i=1;i<=n;i++)
if (cnt[i]) {
ans--;
if (ans) printf("%d ",i);
else printf("%d\n",i);
}
}
return 0;
}