[ZROJ-958]散步 Solution

給你nn個點,每個點有兩種邊,AABB,給出所有點AA邊連向哪個點以及BB邊連向哪個點,(每個點往外只能連一條AA和一條BB)找到一條從11nn的路徑,使得:這條路徑上連續經過的AA路徑或者BB路徑最長長度最短。
看到最後一句話其實就知道這題正解了。
二分。
二分最長長度之後呢?
讓我們從最低檔分開始爬。
對於n500n\leq 500的範圍怎麼辦?把所有點拆成多個點,每層的點表示連續經過若干次AA或若干次BB到達,那麼二分最短長度,判斷連通性即可。
那麼n50000n\leq 50000呢?
上一個東西其實完全沒有使用到每個點往外連的邊:AABB只有一條!這個性質怎麼用?其實你只需要記錄每個點上一次轉移過來的地方是AA還是BB,然後反向走,那麼其實可以讓點和走的距離小於等於二分值的點都連上邊,用倍增優化建圖,然後就可以了。
(其實直接暴力我記得也能過的,這東西寫起來估計比正解還爽)。
正解:
考慮我們的操作,每次都是找到一個被走到的點,然後走kk步,反向,一定是可以保證路徑合法的,所以我們可以用一個隊列加並查集完成這個判定。
如果一個點沒有被訪問過,那麼這個點的父親等於他自身,否則就是不斷往某個方向走到的第一個沒有被訪問過的節點。順便記一下距離,判一下合法性即可。
code:code:

#include <bits/stdc++.h>
#define regi register int
int n,a[2][200001],vis[2][200001],d[2][200001],fa[2][200001],l,r,mid;
std::queue<std::pair<int,int> >q;
inline int read(){
    int r=0,w=0,c;
    for(;!isdigit(c=getchar());r=c);
    for(w=c^48;isdigit(c=getchar());w=w*10+(c^48));
    return r^45?w:-w;
}
inline int find(int op,int x){
	if(fa[op][x]==x)
		return x;
	regi Fa=find(op,fa[op][x]);
	d[op][x]+=d[op][fa[op][x]];
	return fa[op][x]=Fa;
}
bool check(int k){
	for(regi i=0;i<2;++i)
		for(regi j=1;j<=n;++j)
		  vis[i][j]=d[i][j]=0,fa[i][j]=j;
	vis[0][1]=vis[1][1]=1;
	q.push(std::make_pair(0,1));q.push(std::make_pair(1,1));
	while(!q.empty()){
		std::pair<int,int>now=q.front();
		q.pop();
		int op=now.first;
		int x=now.second;
		x=a[op][x];
		for(regi y=x;;){
			y=find(op,y);
			if(vis[op^1][y])
			  break;
			find(op,x);
			if(d[op][x]>=k)
			  break;
			vis[op^1][y]=1;
			q.push(std::make_pair(op^1,y));
			if(find(op,a[op][y])==y)
			  break;
			fa[op][y]=a[op][y];
			d[op][y]++;
		}
	}
	return vis[0][n]|vis[1][n];
}
main(){
	n=read();
	if(n==1){
	  puts("0");
	  return 0;
	}
	for(regi i=1;i<=n;++i){
		a[0][i]=read();
		a[1][i]=read();
	}
	r=n+1;
	while(l+1<r){
		if(check(mid=l+r>>1))
		  r=mid;
		else
		  l=mid;
	}
	printf("%d\n",r==n+1?-1:r);
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章