1. 程式人生 > 實用技巧 >題解 P6722 【「MCOI-01」Village 村莊】

題解 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;
}