洛谷·倉鼠找sugar

初見安~這裏是傳送門:洛谷P3398

題目描述

小倉鼠的和他的基(mei)友(zi)sugar住在地下洞穴中,每個節點的編號爲1~n。地下洞穴是一個樹形結構。這一天小倉鼠打算從從他的臥室(a)到餐廳(b),而他的基友同時要從他的臥室(c)到圖書館(d)。他們都會走最短路徑。現在小倉鼠希望知道,有沒有可能在某個地方,可以碰到他的基友?

小倉鼠那麼弱,還要天天被zzq大爺虐,請你快來救救他吧!

輸入格式

第一行兩個正整數n和q,表示這棵樹節點的個數和詢問的個數。

接下來n-1行,每行兩個正整數u和v,表示節點u到節點v之間有一條邊。

接下來q行,每行四個正整數a、b、c和d,表示節點編號,也就是一次詢問,其意義如上。

輸出格式

對於每個詢問,如果有公共點,輸出大寫字母“Y”;否則輸出“N”。

輸入輸出樣例

輸入 #1

5 5
2 5
4 2
1 3
1 4
5 1 5 1
2 2 1 4
4 1 3 4
3 1 1 5
3 5 1 4

輸出 #1複製

Y
N
Y
Y
Y

說明/提示

__本題時限1s,內存限制128M,因新評測機速度較爲接近NOIP評測機速度,請注意常數問題帶來的影響。__

20%的數據 n<=200,q<=200

40%的數據 n<=2000,q<=2000

70%的數據 n<=50000,q<=50000

100%的數據 n<=100000,q<=100000

題解

說白了就是讓你判斷兩條路徑是否相交。這多大點兒事兒啊!直接樹剖+線段樹給第一條路徑打標記看第二條是否涉及到有標記的點就行了唄!!!!【看官冷靜……還有後文的……】

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 100005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, Q;
struct edge {
	int to, nxt;
	edge() {}
	edge(int tt, int nn) {to = tt, nxt = nn;}
}e[maxn << 1];

int head[maxn], k = 0;
void add(int u, int v) {e[k] = edge(v, head[u]); head[u] = k++;}

int son[maxn], top[maxn], size[maxn], dep[maxn], fa[maxn], dfn[maxn], tot = 0;
bool tree[maxn << 2];
struct HLD {
	void dfs1(int u) {
		size[u] = 1;
		for(int i = head[u]; ~i; i = e[i].nxt) {
			register int v = e[i].to; if(v == fa[u]) continue;
			dep[v] = dep[u] + 1;  fa[v] = u;
			dfs1(v); size[u] += size[v];
			if(size[son[u]] < size[v]) son[u] = v;
		}
	}
	
	void dfs2(int u, int tp) {
		top[u] = tp, dfn[u] = ++tot; if(son[u]) dfs2(son[u], tp);
		for(int i = head[u]; ~i; i = e[i].nxt) {
			register int v = e[i].to;
			if(v != fa[u] && v != son[u]) dfs2(v, v);
		}
	}
	
	void change(int p, int l, int r, int ls, int rs) {//區間標記 
		if(ls <= l && r <= rs) {tree[p] = true; return;}
		int mid = l + r >> 1;
		if(ls <= mid) change(p << 1, l, mid, ls, rs);
		if(rs > mid) change(p << 1 | 1, mid + 1, r, ls, rs);
		tree[p] = tree[p << 1] | tree[p << 1 | 1];
	}
	
	void flag(int u, int v) {//lca打標記 
		while(top[u] != top[v]) {
			if(dep[top[u]] > dep[top[v]]) swap(u, v);
			change(1, 1, n, dfn[top[v]], dfn[v]);
			v = fa[top[v]];
		}
		if(dep[u] > dep[v]) swap(u, v);
		change(1, 1, n, dfn[u], dfn[v]);
	}
	
	bool ask(int p, int l, int r, int ls, int rs) {//查詢是否有標記 
		if(ls <= l && r <= rs) return tree[p];
		int mid = l + r >> 1, ans = 0;
		if(!tree[p << 1] && !tree[p << 1 | 1]) tree[p << 1] |= tree[p], tree[p << 1 | 1] |= tree[p];
		//上面這句判斷兩區間是否都沒有標記很重要 
		if(ls <= mid) ans |= ask(p << 1, l, mid, ls, rs);
		if(rs > mid) ans |= ask(p << 1 | 1, mid + 1, r, ls, rs);
		return ans;
	}
	
	bool check(int u, int v) {//檢查是否有覆蓋到路徑1的標記 
		while(top[u] != top[v]) {
			if(dep[top[u]] > dep[top[v]]) swap(u, v);			
			if(ask(1, 1, n, dfn[top[v]], dfn[v])) {return true;}
			v = fa[top[v]];
		}
		
		if(dep[u] > dep[v]) swap(u, v);
		if(ask(1, 1, n, dfn[u], dfn[v])) return true;
		return false;
	}
}H;

signed main() {
	memset(head, -1, sizeof head);
	n = read(), Q = read();
	for(int u, v, i = 1; i < n; i++) u = read(), v = read(), add(u, v), add(v, u);

	H.dfs1(1), H.dfs2(1, 1);
	register int s1, s2, t1, t2;
	while(Q--) {
		s1 = read(), t1 = read(), s2 = read(), t2 = read();
		memset(tree, 0, sizeof tree);//線段樹 
		if(s1 == s2 || t1 == t2) {puts("Y"); continue;}
		H.flag(s1, t1);//給第一條路徑打標記 
		if(H.check(s2, t2)) puts("Y");
		else puts("N");
	}
	return 0;
}

然後——這就是在TLE的邊緣試探啊。

所以我想了想——一定是那個memset耗時太長。所以我們只有捨棄線段樹往正解考慮了。【大霧

如果兩條路徑有交集,那麼其中一個的LCA一定在另一個的路徑上

這條結論比較好證明——主要是抓住樹形結構從下往上是形如合併的關係就很好理解了,可以手動舉一些例子來看。

那麼我是不是直接看【樹剖思想中】其中一個的lca在線段樹上的dfn值【就是重鏈剖分後的序】是不是存在於另一條路徑的某個區間內不就行了?

主要是這樣就可以去掉memset了……瞬間就可以快很多。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 100005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, Q;
struct edge {
	int to, nxt;
	edge() {}
	edge(int tt, int nn) {to = tt, nxt = nn;}
}e[maxn << 1];

int head[maxn], k = 0;
void add(int u, int v) {e[k] = edge(v, head[u]); head[u] = k++;}

int son[maxn], top[maxn], size[maxn], dep[maxn], fa[maxn], dfn[maxn], tot = 0;
bool tree[maxn << 2];
struct HLD {
	void dfs1(int u) {
		size[u] = 1;
		for(int i = head[u]; ~i; i = e[i].nxt) {
			register int v = e[i].to; if(v == fa[u]) continue;
			dep[v] = dep[u] + 1;  fa[v] = u;
			dfs1(v); size[u] += size[v];
			if(size[son[u]] < size[v]) son[u] = v;
		}
	}
	
	void dfs2(int u, int tp) {
		top[u] = tp, dfn[u] = ++tot; if(son[u]) dfs2(son[u], tp);
		for(int i = head[u]; ~i; i = e[i].nxt) {
			register int v = e[i].to;
			if(v != fa[u] && v != son[u]) dfs2(v, v);
		}
	}
	
	int LCA(int u, int v) {//求LCA 
		while(top[u] != top[v]) {
			if(dep[top[u]] > dep[top[v]]) swap(u, v);			
			v = fa[top[v]];
		}
		
		if(dep[u] > dep[v]) swap(u, v);
		return u;	
	}
	
	bool check(int lca, int u, int v) {//檢驗是否在路徑上 
		lca = dfn[lca];
		while(top[u] != top[v]) {
			if(dep[top[u]] > dep[top[v]]) swap(u, v);
			if(dfn[top[v]] <= lca && lca <= dfn[v]) return true;			
			v = fa[top[v]];
		}
		
		if(dep[u] > dep[v]) swap(u, v);
		if(dfn[u] <= lca && lca <= dfn[v]) return true;
		return false;
	}
}H;

signed main() {
	memset(head, -1, sizeof head);
	n = read(), Q = read();
	for(int u, v, i = 1; i < n; i++) u = read(), v = read(), add(u, v), add(v, u);

	H.dfs1(1), H.dfs2(1, 1);
	register int s1, s2, t1, t2;
	while(Q--) {
		s1 = read(), t1 = read(), s2 = read(), t2 = read();
		if(s1 == s2 || t1 == t2) {puts("Y"); continue;}
		int lca1 = H.LCA(s1, t1), lca2 = H.LCA(s2, t2);	
			
		if(H.check(lca1, s2, t2)) puts("Y");//檢查
		else if(H.check(lca2, s1, t1)) puts("Y");
		else puts("N");
	}
	return 0;
}

這樣一來就可以快很多了,加上樹剖求LCA本就比倍增塊,所以總耗時就只有200+ms

當然,再讓步驟簡單一點兒,總時間可以快5ms【???????

也就是說我們還要得出一個結論。

在剛剛那條結論的基礎上,我們繼續展開——既然其中一個的LCA一定在另一條的路徑上,也就是說這個LCA一定在另一條路徑的起點或者終點到其LCA的路徑上。而因爲樹的形態,我們可以忽略起點和終點的概念,就當是兩個點,理解爲路徑不管是折鏈還是直鏈我們都要先讓下面那個點往上走。所以我們需要我們具體一點:假設有兩條路徑,s1->t1, s2->t2,其LCA分別爲lca1,lca2,並且有dep[lca1]>dep[lca2],那麼當且僅當lca1在路徑s2->lca2或者t2->lca2的時候兩條路徑有公共部分。換言之,lca1要麼在s2的上方,要麼在t2的上方並且不超過lca2。也就是dep[lca1] == dep[LCA(s2, lca1)] 或者dep[lca1] == dep[LCA(t2, lca1)]

這樣解的話就和樹剖的知識沒有任何關係了,直接求解LCA即可。

因爲本狸比較懶,所以最後求LCA的方法還是樹剖【大霧。主要看核心代碼啦~

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 100005
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, Q;
struct edge {
	int to, nxt;
	edge() {}
	edge(int tt, int nn) {to = tt, nxt = nn;}
}e[maxn << 1];

int head[maxn], k = 0;
void add(int u, int v) {e[k] = edge(v, head[u]); head[u] = k++;}

int son[maxn], top[maxn], size[maxn], dep[maxn], fa[maxn], dfn[maxn], tot = 0;
bool tree[maxn << 2];
struct HLD {
	void dfs1(int u) {
		size[u] = 1;
		for(int i = head[u]; ~i; i = e[i].nxt) {
			register int v = e[i].to; if(v == fa[u]) continue;
			dep[v] = dep[u] + 1;  fa[v] = u;
			dfs1(v); size[u] += size[v];
			if(size[son[u]] < size[v]) son[u] = v;
		}
	}
	
	void dfs2(int u, int tp) {
		top[u] = tp, dfn[u] = ++tot; if(son[u]) dfs2(son[u], tp);
		for(int i = head[u]; ~i; i = e[i].nxt) {
			register int v = e[i].to;
			if(v != fa[u] && v != son[u]) dfs2(v, v);
		}
	}
	
	int LCA(int u, int v) {
		while(top[u] != top[v]) {
			if(dep[top[u]] > dep[top[v]]) swap(u, v);			
			v = fa[top[v]];
		}
		
		if(dep[u] > dep[v]) swap(u, v);
		return u;	
	}
	
	bool check(int lca, int u, int v) {
		lca = dfn[lca];
		while(top[u] != top[v]) {
			if(dep[top[u]] > dep[top[v]]) swap(u, v);
			if(dfn[top[v]] <= lca && lca <= dfn[v]) return true;			
			v = fa[top[v]];
		}
		
		if(dep[u] > dep[v]) swap(u, v);
		if(dfn[u] <= lca && lca <= dfn[v]) return true;
		return false;
	}
}H;

signed main() {
	memset(head, -1, sizeof head);
	n = read(), Q = read();
	for(int u, v, i = 1; i < n; i++) u = read(), v = read(), add(u, v), add(v, u);

	H.dfs1(1), H.dfs2(1, 1);
	register int s1, s2, t1, t2;
	while(Q--) {
		s1 = read(), t1 = read(), s2 = read(), t2 = read();
		if(s1 == s2 || t1 == t2) {puts("Y"); continue;}
		int lca1 = H.LCA(s1, t1), lca2 = H.LCA(s2, t2);		
		if(max(dep[s1], dep[t1]) < dep[lca2] || max(dep[s2], dep[t2]) < dep[lca1]) puts("N");
        //這裏預判無解很重要,避免被正解判定判錯
		else {
			if(dep[lca1] < dep[lca2]) swap(lca1, lca2), swap(s1, s2), swap(t1, t2);
            //這裏我們就直接用lca1,所以如果深度不符合要求要先交換
			if(dep[lca1] == dep[H.LCA(lca1, s2)] || dep[lca1] == dep[H.LCA(lca1, t2)]) puts("Y");
			else puts("N");
		}
		
	}
	return 0;
}

以上就是全部內容啦!!!!!!!!!!!

迎評:)
——End——

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章