題解 P6722 【「MCOI-01」Village 村莊】
Step1 前置芝士:
如何證明一個圖是二分圖,又如何證明一個圖不是二分圖?
從定義入手: 二分圖是一種不含有奇數條邊環的圖。
所以,對於此題,如果我們能確定新圖中是否含有基環,我們就相應的可以確定這個資料合不合法。
Step2 從暴力開始
考慮如何暴力建新圖?
如果$dis_{u, v} \ge k$,那麼在新圖中就可以有連邊唄。
所以,求出全源最短路,然後暴力建新圖,最後判斷是否為二分圖就是我們的暴力做法了。
(~~感覺這個暴力也很麻煩的亞子,還不如繼續推寫正解(滑稽~~)
Step 3 考慮基環的性質
在一個基環上,我們考慮每個點的編號為$a_1, a_2, a_3$ ~ $a_m$。
其中$a_m$為編號最大的一個基環上的點, 令 $m > 3$。
假設基環上不存在一個點$a_p$, 滿足$dis(a_1, a_p) \ge k$ ^ $dis(a_p, a_m) \ge k$,即不滿足存在三元環的條件
不妨利用數學歸納法:
設一點$a_x$存在於基環上。
當 $x = 1$時,顯然有 $dis(a_1, a_m) \ge k$ ^ $dis(a_1, a_2) \ge k$
當$x = 2$時,顯然有 $dis(a_2, a_3) \ge k$ ^ $dis(a_2, a_m) < k$
當$x = 2y(y ≠0)$時,有 $dis(a_{2y}, a_{2y+1}) \ge k$ ^ $dis(a_{2y},a_1) < k$ ^ $dis(a_{2y}, a_m) < k$ $\Rightarrow$ 可知,此時的$a_m$不能存在於基環中,顯然不對。
(這裡還要結合原圖來看啊,建議大家結合樣例來手摸。)
(基環)
那麼,證明了圖中存在三元環之後,~~我們來看看官方題解~~,看看三元環跟樹的直徑的關係。
假設圖中1,2,3號點構成三元環。
那麼,有$d + e \ge k$, $d + f \ge k$, $e + f \ge k$.
令圖中$a + b$為極大值,則 $4,5,6$為樹的直徑。
假設不存在一點到直徑兩端點可以構成三元環。
那麼,令1號點等效於1,2,3號點。
為滿足條件,我們有:
$d + c + a < k$, 配合前面的柿子:
$d + e \ge k$
不等式基本原則,$a + c < e$
所以 $b + c + e > b + c + a + c > a + b$,此時$(2,6)$成為直徑,不符合原圖。
所以,三元環上一定有一點到直徑的兩端距離均大於$k$。於是,我們只要找出樹的直徑,然後判斷是否有點到其兩端的距離均$\ge k$即可。如果存在,則原圖存在基環,不成立。
#include <bits/stdc++.h> using namespace std; #define N 100010 template <class T> inline void read(T& a){ T x = 0, s = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); } a = x * s; return ; } struct node{ public: int v, w, next; public: node(int v = 0, int w = 0, int next = 0){ this -> v = v; this -> w = w; this -> next = next; return ; }// 為什麼存個邊要寫得這麼毒瘤? 因為方便賦予初值啊 public: inline void clean(){ this -> v = 0; this -> next = 0; this -> w = 0; return ; } } t[N << 1]; int f[N]; int bian = 0; inline void add(int u, int v, int w){ t[++bian] = node(v, w, f[u]), f[u] = bian; t[++bian] = node(u, w, f[v]), f[v] = bian; return ; } int dis[N], dis1[N], dis2[N]; int s1, s2; int maxn = -666; int cur = 0; #define v t[i].v void dfs(int now, int father, int* dis){ // 用指標方便傳入dis陣列 if(dis[now] > maxn){ maxn = dis[now]; cur = now; } for(int i = f[now]; i; i = t[i].next){ if(v != father){ dis[v] = dis[now] + t[i].w; dfs(v, now, dis); } } return ; } #undef v inline void search(int x, int& y, int* dis){ // 找端點等各種操作 maxn = -666; cur = 0; dis[x] = 0; dfs(x, 0, dis); y = cur; return ; } inline void clean(){ // 不要忘記清空 for(int i = 1;i <= bian; i++) t[i].clean(); memset(f, 0, sizeof(f)); bian = 0; return ; } int main(){ int T; read(T); while(T--){ clean(); int n, k; read(n), read(k); s1 = 0, s2 = 0; for(int i = 1; i < n; i++){ int x, y, w; read(x), read(y), read(w); add(x, y, w); } search(1, s1, dis); search(s1, s2, dis1); // 找端點,順便記錄dis1 int temp = 0; search(s2, temp, dis2); // 多搜一次,找dis2 bool flag = 0; for(int i = 1;i <= n; i++) if(dis1[i] >= k && dis2[i] >= k) { flag = 1; break; } if(flag)puts("Baka Chino"); else puts("Yes"); } return 0; }