hdu 4674 Trip Advisor(缩点+倍增lca)

时间:2024-08-01 19:36:56

花了一天半的时间,才把这道题ac= =

确实是道好题,好久没敲这么长的code了,尤其是最后的判定,各种销魂啊~

题目中给出的条件最值得关注的就是:每个点最多只能在一个环内->原图是由一个个边连通分量以树形连接组成的->做无向图缩点后,得到的是一个树形结构。

题目要求:u->v,必须经过p,且不能重复经过同一个点,即在树上从u到v做一笔画。

开始先想到汉密尔顿迹,不过那是走全部点的。利用已获得的树形结构,通过lca来判断p,这就是一个合理的作法。

注意:由于是任意建树,p不一定是u,v的lca,纠结了好久才想出了一个方法:x=lca(u,v),然后遍历v->x,u->x两条路径,找p——会超时的,太繁琐了。朋友提出了,找u,v,p中两两的lca:if(lca(u,v)==lca(u,p)&&lca(v,p)==p)就可以判断缩点后的p在u,v路径上。为什么是缩点后的呢?自己想吧,后面判断要用到的。

缩点和倍增lca自己学吧,我也是为了做这道题才学了lca T^T 之前连“爬楼梯”都不会。

这道题的精彩之处就在于最后的判定。

从u==v&&v==p 三点共点->两点共点,再到缩点后三点共点,两点共点要一一讨论。

其中容易忽视的:1、u,p缩点后cu,cp共点,点u在向cv出发的方向的出口上。(若要经过p,u点必然要重复经过)

2、环cp在u->v的路径上,但进出在同一点,并且不是点p。(若要经过p,该出入点必然要重复经过)

其他一些容易犯的错误我都在code中注明。不过这道题的价值也就体现于此,自己做吧。再次膜拜现场出题的大牛。

注:TLE了好久,后来就wa,为了调程序,编了一组七个点的数据,从111->777共344组测试,code中附上了数据以及自己代码中找到的三组错误测试,修正后就ac了。

 #pragma comment(linker,"/STACK:1024000000,1024000000")
#include<cstdio>
#include<cstring>
#include<stack>
#include<queue>
#include<algorithm>
using namespace std; const int MAXN=;
const int MAXM=;
const int POW=; struct Edge{
int v,next;
int vis,bridge;
Edge(){}
Edge(int _v,int _next):v(_v),next(_next),vis(){}
}edge[MAXM<<]; int head[MAXN],tol;
int stk[MAXN],top;
int pre[MAXN],low[MAXN],bccno[MAXN],bcc_cnt,dfs_clock;
vector<int>G[MAXN];
int d[MAXN],p[MAXN][POW]; void init()
{
tol=;
memset(head,-,sizeof(head));
} void add(int u,int v)
{
edge[tol]=Edge(v,head[u]);
head[u]=tol++;
} void build(int m)
{
int u,v;
init();
for(int i=;i<=m;i++)
{
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
} void dfs(int u)
{
int v;
pre[u]=low[u]=++dfs_clock;
stk[top++]=u;
for(int i=head[u];i!=-;i=edge[i].next)
{
if(edge[i].vis)
continue ;
edge[i].vis=edge[i^].vis=;
v=edge[i].v; if(!pre[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}else if(!bccno[v])
low[u]=min(low[u],pre[v]);
} if(low[u]==pre[u]){
bcc_cnt++;
do{
v=stk[--top];
bccno[v]=bcc_cnt;
}while(v!=u);
}
} void tarjin(int n)
{
bcc_cnt=dfs_clock=;
memset(pre,,sizeof(pre));
memset(bccno,,sizeof(bccno)); top=;
for(int i=;i<=n;i++)
if(!pre[i])
dfs(i);
} void rebuild(int n)
{
for(int i=;i<=bcc_cnt;i++) //!!把bcc_cnt写成n = =
G[i].clear();
for(int i=;i<=n;i++)
{ for(int j=head[i];j!=-;j=edge[j].next)
{
int v=edge[j].v;
if(bccno[i]!=bccno[v])
G[bccno[i]].push_back(v); //!!桥,这里写v而不是bccno[v],是为了后面judge()判断环的出入口
}
}
} void DFS(int u,int fa)
{
d[u]=d[fa]+;
p[u][]=fa;
for(int i=;i<POW;i++)
p[u][i]=p[p[u][i-]][i-];
int sz=G[u].size();
for(int i=;i<sz;i++)
{
int v=bccno[G[u][i]];
if(v==fa)
continue;
DFS(v,u);
}
} int lca( int a, int b ){
if( d[a] > d[b] ) a ^= b, b ^= a, a ^= b;
if( d[a] < d[b] ){
int del = d[b] - d[a];
for( int i = ; i < POW; i++ ) if(del&(<<i)) b=p[b][i];
}
if( a != b ){
for( int i = POW-; i >= ; i-- )
if( p[a][i] != p[b][i] )
a = p[a][i] , b = p[b][i];
a = p[a][], b = p[b][];
}
return a;
} void LCA(int n)//这里只是处理了一下新图中各个点的深度
{
d[]=;
DFS(,);
} int Double(int a,int b)//倍增
{
int del=d[a]-d[b]-;
for(int i=;i<POW;i++)
if(del&(<<i))
a=p[a][i];
return a;
} int judge(int u,int v)//返回值是该方向上环的出入口
{
int sz=G[u].size();
for(int i=;i<sz;i++)
if(bccno[G[u][i]]==bccno[v])
return G[u][i];
} void solve()
{
int k;
int u,v,w;
scanf("%d",&k);
for(int i=;i<=k;i++)
{
scanf("%d%d%d",&u,&v,&w);
int a=bccno[u],b=bccno[v],c=bccno[w];
if(u==v&&v==w)
printf("Yes\n");
else if(u==w||v==w)//!!注意:即使考虑了两点uw(或vw)缩点后重合,目标点u(或v)不能作为出口,但u,w共点是可行的
printf("Yes\n");
else if(u==v)
printf("No\n");
else if(bccno[u]==bccno[v]&&bccno[u]==bccno[w])
printf("Yes\n");
else if(bccno[u]==bccno[v])
printf("No\n");
else if(bccno[u]==bccno[w]){
int flog;
int pp=lca(a,b);//!!注意:当两点缩点后重合,无法直接判定第三点在上方还是下方,需要利用lca
if(pp==a){
b=Double(b,c);//由b向c做倍增
flog=judge(b,u);
}else
flog=judge(p[a][],u);
if(flog!=u)
printf("Yes\n");
else
printf("No\n");
}else if(bccno[v]==bccno[w]){
int flog;
int pp=lca(a,b);//!!同上
if(pp==b){
a=Double(a,c);//由a向c做倍增
flog=judge(a,v);
}else
flog=judge(p[b][],v);
if(flog!=v)
printf("Yes\n");
else
printf("No\n");
}else {
int p1,p2,p3; p1=lca(a,b);
p2=lca(a,c);
p3=lca(b,c);
if(p1==p2&&p3==c){//判定点p是否能经过:求出两两lca
int flog1,flog2;
b=Double(b,c);
flog1=judge(b,w); if(p1==c){//!!注意:当点p即为lca(u,v)时,p的另一个入口就不能从其父亲查找了,要在另一支路上做倍增
a=Double(a,c);
flog2=judge(a,w);
}else
flog2=judge(p[c][],w); if(flog1!=w&&flog2!=w&&flog1==flog2)
printf("No\n");
else
printf("Yes\n");
}
else if(p1==p3&&p2==c){//这里上下两步其实是重复处理:p3==c表示p点在v支路上;反之在u支路上
int flog1,flog2;
a=Double(a,c);
flog1=judge(a,w); if(p1==c){
b=Double(b,c);
flog2=judge(b,w);
}else
flog2=judge(p[c][],w); if(flog1!=w&&flog2!=w&&flog1==flog2)
printf("No\n");
else
printf("Yes\n");
}else
printf("No\n");
}
}
} int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
build(m);
tarjin(n);
rebuild(n);
LCA(n);
solve();
}
return ;
}
/*
7 7
1 2
2 3
2 4
3 4
3 5
4 6
4 7
3
1 2 3
1 6 2
1 2 1
*/