花了一天半的时间,才把这道题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了。
1 #pragma comment(linker,"/STACK:1024000000,1024000000") 2 #include<cstdio> 3 #include<cstring> 4 #include<stack> 5 #include<queue> 6 #include<algorithm> 7 using namespace std; 8 9 const int MAXN=111111; 10 const int MAXM=155555; 11 const int POW=18; 12 13 struct Edge{ 14 int v,next; 15 int vis,bridge; 16 Edge(){} 17 Edge(int _v,int _next):v(_v),next(_next),vis(0){} 18 }edge[MAXM<<1]; 19 20 int head[MAXN],tol; 21 int stk[MAXN],top; 22 int pre[MAXN],low[MAXN],bccno[MAXN],bcc_cnt,dfs_clock; 23 vector<int>G[MAXN]; 24 int d[MAXN],p[MAXN][POW]; 25 26 void init() 27 { 28 tol=0; 29 memset(head,-1,sizeof(head)); 30 } 31 32 void add(int u,int v) 33 { 34 edge[tol]=Edge(v,head[u]); 35 head[u]=tol++; 36 } 37 38 void build(int m) 39 { 40 int u,v; 41 init(); 42 for(int i=1;i<=m;i++) 43 { 44 scanf("%d%d",&u,&v); 45 add(u,v); 46 add(v,u); 47 } 48 } 49 50 void dfs(int u) 51 { 52 int v; 53 pre[u]=low[u]=++dfs_clock; 54 stk[top++]=u; 55 for(int i=head[u];i!=-1;i=edge[i].next) 56 { 57 if(edge[i].vis) 58 continue ; 59 edge[i].vis=edge[i^1].vis=1; 60 v=edge[i].v; 61 62 if(!pre[v]){ 63 dfs(v); 64 low[u]=min(low[u],low[v]); 65 }else if(!bccno[v]) 66 low[u]=min(low[u],pre[v]); 67 } 68 69 if(low[u]==pre[u]){ 70 bcc_cnt++; 71 do{ 72 v=stk[--top]; 73 bccno[v]=bcc_cnt; 74 }while(v!=u); 75 } 76 } 77 78 void tarjin(int n) 79 { 80 bcc_cnt=dfs_clock=0; 81 memset(pre,0,sizeof(pre)); 82 memset(bccno,0,sizeof(bccno)); 83 84 top=0; 85 for(int i=1;i<=n;i++) 86 if(!pre[i]) 87 dfs(i); 88 } 89 90 void rebuild(int n) 91 { 92 for(int i=1;i<=bcc_cnt;i++) //!!把bcc_cnt写成n = = 93 G[i].clear(); 94 for(int i=1;i<=n;i++) 95 { 96 97 for(int j=head[i];j!=-1;j=edge[j].next) 98 { 99 int v=edge[j].v; 100 if(bccno[i]!=bccno[v]) 101 G[bccno[i]].push_back(v); //!!桥,这里写v而不是bccno[v],是为了后面judge()判断环的出入口 102 } 103 } 104 } 105 106 void DFS(int u,int fa) 107 { 108 d[u]=d[fa]+1; 109 p[u][0]=fa; 110 for(int i=1;i<POW;i++) 111 p[u][i]=p[p[u][i-1]][i-1]; 112 int sz=G[u].size(); 113 for(int i=0;i<sz;i++) 114 { 115 int v=bccno[G[u][i]]; 116 if(v==fa) 117 continue; 118 DFS(v,u); 119 } 120 } 121 122 int lca( int a, int b ){ 123 if( d[a] > d[b] ) a ^= b, b ^= a, a ^= b; 124 if( d[a] < d[b] ){ 125 int del = d[b] - d[a]; 126 for( int i = 0; i < POW; i++ ) if(del&(1<<i)) b=p[b][i]; 127 } 128 if( a != b ){ 129 for( int i = POW-1; i >= 0; i-- ) 130 if( p[a][i] != p[b][i] ) 131 a = p[a][i] , b = p[b][i]; 132 a = p[a][0], b = p[b][0]; 133 } 134 return a; 135 } 136 137 void LCA(int n)//这里只是处理了一下新图中各个点的深度 138 { 139 d[1]=0; 140 DFS(1,1); 141 } 142 143 int Double(int a,int b)//倍增 144 { 145 int del=d[a]-d[b]-1; 146 for(int i=0;i<POW;i++) 147 if(del&(1<<i)) 148 a=p[a][i]; 149 return a; 150 } 151 152 int judge(int u,int v)//返回值是该方向上环的出入口 153 { 154 int sz=G[u].size(); 155 for(int i=0;i<sz;i++) 156 if(bccno[G[u][i]]==bccno[v]) 157 return G[u][i]; 158 } 159 160 void solve() 161 { 162 int k; 163 int u,v,w; 164 scanf("%d",&k); 165 for(int i=1;i<=k;i++) 166 { 167 scanf("%d%d%d",&u,&v,&w); 168 int a=bccno[u],b=bccno[v],c=bccno[w]; 169 if(u==v&&v==w) 170 printf("Yes "); 171 else if(u==w||v==w)//!!注意:即使考虑了两点uw(或vw)缩点后重合,目标点u(或v)不能作为出口,但u,w共点是可行的 172 printf("Yes "); 173 else if(u==v) 174 printf("No "); 175 else if(bccno[u]==bccno[v]&&bccno[u]==bccno[w]) 176 printf("Yes "); 177 else if(bccno[u]==bccno[v]) 178 printf("No "); 179 else if(bccno[u]==bccno[w]){ 180 int flog; 181 int pp=lca(a,b);//!!注意:当两点缩点后重合,无法直接判定第三点在上方还是下方,需要利用lca 182 if(pp==a){ 183 b=Double(b,c);//由b向c做倍增 184 flog=judge(b,u); 185 }else 186 flog=judge(p[a][0],u); 187 if(flog!=u) 188 printf("Yes "); 189 else 190 printf("No "); 191 }else if(bccno[v]==bccno[w]){ 192 int flog; 193 int pp=lca(a,b);//!!同上 194 if(pp==b){ 195 a=Double(a,c);//由a向c做倍增 196 flog=judge(a,v); 197 }else 198 flog=judge(p[b][0],v); 199 if(flog!=v) 200 printf("Yes "); 201 else 202 printf("No "); 203 }else { 204 int p1,p2,p3; 205 206 p1=lca(a,b); 207 p2=lca(a,c); 208 p3=lca(b,c); 209 if(p1==p2&&p3==c){//判定点p是否能经过:求出两两lca 210 int flog1,flog2; 211 b=Double(b,c); 212 flog1=judge(b,w); 213 214 if(p1==c){//!!注意:当点p即为lca(u,v)时,p的另一个入口就不能从其父亲查找了,要在另一支路上做倍增 215 a=Double(a,c); 216 flog2=judge(a,w); 217 }else 218 flog2=judge(p[c][0],w); 219 220 if(flog1!=w&&flog2!=w&&flog1==flog2) 221 printf("No "); 222 else 223 printf("Yes "); 224 } 225 else if(p1==p3&&p2==c){//这里上下两步其实是重复处理:p3==c表示p点在v支路上;反之在u支路上 226 int flog1,flog2; 227 a=Double(a,c); 228 flog1=judge(a,w); 229 230 if(p1==c){ 231 b=Double(b,c); 232 flog2=judge(b,w); 233 }else 234 flog2=judge(p[c][0],w); 235 236 if(flog1!=w&&flog2!=w&&flog1==flog2) 237 printf("No "); 238 else 239 printf("Yes "); 240 }else 241 printf("No "); 242 } 243 } 244 } 245 246 int main() 247 { 248 int n,m; 249 while(~scanf("%d%d",&n,&m)) 250 { 251 build(m); 252 tarjin(n); 253 rebuild(n); 254 LCA(n); 255 solve(); 256 } 257 return 0; 258 } 259 /* 260 7 7 261 1 2 262 2 3 263 2 4 264 3 4 265 3 5 266 4 6 267 4 7 268 3 269 1 2 3 270 1 6 2 271 1 2 1 272 */