#pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <cstring> #include <cmath> #include <vector> #include <iostream> #include <algorithm> using namespace std; const int maxn=40004; struct node{ int to,w; node(int a=0,int b=0){to=a;w=b;} }; vector<node>e[maxn]; int f[maxn],dis[maxn],deep[maxn],p[maxn][20],n; void dfs(int u,int pre,int t) { int i,num; deep[u]=t;//深度 f[u]=pre;//父节点 num=e[u].size(); for(i=0;i<num;i++) { int v=e[u][i].to; if(v!=pre) { dis[v]=dis[u]+e[u][i].w;//距离跟的距离 dfs(v,u,t+1); } } } void init() { //p[i][j]表示i结点的第2^j祖先 int i,j; for(j=0;(1<<j)<=n;j++) for(i=1;i<=n;i++) p[i][j]=-1; for(i=1;i<=n;i++)p[i][0]=f[i]; for(j=1;(1<<j)<=n;j++) for(i=1;i<=n;i++) if(p[i][j-1]!=-1) p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先 } int lca(int a,int b)//最近公共祖先 { int i,j; if(deep[a]<deep[b])swap(a,b); for(i=0;(1<<i)<=deep[a];i++); i--; //使a,b两点的深度相同 for(j=i;j>=0;j--) if(deep[a]-(1<<j)>=deep[b]) a=p[a][j]; if(a==b)return a; //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点 for(j=i;j>=0;j--) { if(p[a][j]!=-1&&p[a][j]!=p[b][j]) { a=p[a][j]; b=p[b][j]; } } return f[a]; } int main() { int T; scanf("%d",&T); while(T--) { int m,i,a,b,c,ans; scanf("%d%d",&n,&m); for(i=1;i<=n;i++)e[i].clear(); for(i=1;i<n;i++) { scanf("%d%d%d",&a,&b,&c); e[a].push_back(node(b,c)); e[b].push_back(node(a,c)); } dis[1]=0; dfs(1,-1,0);//找到各点的深度和各点的父节点以及距离根的距离 init(); //初始各个点的2^j祖先是谁 for(i=0;i<m;i++) { scanf("%d%d",&a,&b); ans=dis[a]+dis[b]-2*dis[lca(a,b)]; printf("%d\n",ans); } } return 0; } /* 最近公共祖先lca,在线算法/倍增法,模板题。套别人模板自己敲了遍,现在还要回顾下邻接表,哎。。 用vector,发现爆栈了,汗一个。。 用#pragma comment(linker, "/STACK:1024000000,1024000000") 开个把栈开大点吧。。hdu可以,别的地方就不清楚了 */
下面是用邻接表存图,经证明栈内存不够主要应该是在线运算太耗了的缘故吧。。
#pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <cstring> #include <cmath> #include <vector> #include <iostream> #include <algorithm> using namespace std; const int maxn=40004; struct node{ int to,w,next; }e[maxn*2]; int f[maxn],dis[maxn],deep[maxn],p[maxn][20],n,tt,head[maxn]; void add(int a,int b,int c) { e[tt].to=b; e[tt].w=c; e[tt].next=head[a]; head[a]=tt++; } void dfs(int u,int pre,int t) { int i,num; deep[u]=t;//深度 f[u]=pre;//父节点 for(i=head[u];i!=-1;i=e[i].next) { int v=e[i].to; if(v!=pre) { dis[v]=dis[u]+e[i].w;//距离跟的距离 dfs(v,u,t+1); } } } void init() { //p[i][j]表示i结点的第2^j祖先 int i,j; for(j=0;(1<<j)<=n;j++) for(i=1;i<=n;i++) p[i][j]=-1; for(i=1;i<=n;i++)p[i][0]=f[i]; for(j=1;(1<<j)<=n;j++) for(i=1;i<=n;i++) if(p[i][j-1]!=-1) p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先 } int lca(int a,int b)//最近公共祖先 { int i,j; if(deep[a]<deep[b])swap(a,b); for(i=0;(1<<i)<=deep[a];i++); i--; //使a,b两点的深度相同 for(j=i;j>=0;j--) if(deep[a]-(1<<j)>=deep[b]) a=p[a][j]; if(a==b)return a; //倍增法,每次向上进深度2^j,找到最近公共祖先的子结点 for(j=i;j>=0;j--) { if(p[a][j]!=-1&&p[a][j]!=p[b][j]) { a=p[a][j]; b=p[b][j]; } } return f[a]; } int main() { int T; scanf("%d",&T); while(T--) { int m,i,a,b,c,ans; scanf("%d%d",&n,&m); memset(head,-1,sizeof(head)); tt=0; for(i=1;i<n;i++) { scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); } dis[1]=0; dfs(1,-1,0);//找到各点的深度和各点的父节点以及距离根的距离 init(); //初始各个点的2^j祖先是谁 for(i=0;i<m;i++) { scanf("%d%d",&a,&b); ans=dis[a]+dis[b]-2*dis[lca(a,b)]; printf("%d\n",ans); } } return 0; }
下面是离线算法,我想应该不会栈内存不够啊,为什么还是爆。。
#pragma comment(linker, "/STACK:1024000000,1024000000") #include <cstdio> #include <cstring> #include <cmath> #include <vector> #include <iostream> #include <algorithm> using namespace std; const int maxn=40004; struct node{ int to,w; node(int a=0,int b=0){to=a;w=b;} }; int f[maxn],dis[maxn],n,ans[maxn],vis[maxn]; //f[i]并查集所用,记录前继结点 //dis[i]记录个点到跟结点的距离 //ans记录m个询问的答案 //vis标记查询过了的点 vector<node>e[maxn];//记录树 vector<node>q[maxn];//记录所求最短距离的两点 int find(int x) { if(x!=f[x])f[x]=find(f[x]); return f[x]; } void lca(int u) { int i,j,k,v,c; for(i=0;i<e[u].size();i++) { v=e[u][i].to; if(vis[v])continue; vis[v]=1; dis[v]=dis[u]+e[u][i].w; lca(v);//深度优先搜索 f[v]=u; for(j=0;j<q[v].size();j++) { c=q[v][j].to; //如果所求两点中的对应点已知,则必定在同一子树上,由于并查集只记录所在子树。所以find(c),就是最近公共祖先 if(vis[c]&&ans[q[v][j].w]==-1) { if(v==c)ans[q[v][j].w]=0;//自成环 else ans[q[v][j].w]=dis[v]+dis[c]-2*dis[find(c)]; } } } } int main() { int T; scanf("%d",&T); while(T--) { int i,j,k,a,b,c,m,aa,bb; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { e[i].clear(); q[i].clear(); ans[i]=-1; f[i]=i; vis[i]=0; } for(i=1;i<n;i++) { scanf("%d%d%d",&a,&b,&c); e[a].push_back(node(b,c)); e[b].push_back(node(a,c)); } for(i=1;i<=m;i++) { scanf("%d%d",&a,&b); q[a].push_back(node(b,i)); q[b].push_back(node(a,i)); } vis[1]=1; dis[1]=0; lca(1); for(i=1;i<=m;i++) printf("%d\n",ans[i]); } return 0; } /* 最近公共祖先lca 离线算法/Tarjan算法 方法举例说明: 1 / \ 2 3 / \ 4 5 / / 7 8 / 9 查询(4,9):到4时,由于vis[9]=0,所以继续;到9后,最近公共祖先就是f[4]=4(回溯的时候记录父节点); 查询(9,8):深度优先搜索,所以到8时才询问;要到8必须回溯到2,在进到8这个子树,所以以记录f[9]=7;f[7]=4;f[4]=2;f[2]=2;所以find(2)=2; 查询(8,3):跟上条相似,必须回溯1才能到3,而find(8)=1就是最近公共祖先; 我们可以发现,对于查询的两点,都要在先查询到的点开始,回溯到最近公共祖先,才查询相对应的点。这也就是离线算法的思路了。。这是我自己理解的。。 下面是专业说明,反正我是没看懂。。 利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。 Tarjan算法基于深度优先搜索的框架,对于新搜索到 的一个结点,首先创建由这个结点构成的集合, 再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。 其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。 之后继续搜索下一棵子树,直到当前结点的所 有子树搜索完。这时把当前结点也设为已被检查过的, 同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问, 且v已被检查过,则由于 进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查, 而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v 所在集合的祖先。 */