1. 程式人生 > 實用技巧 >【HNOI2009】 最小圈

【HNOI2009】 最小圈

最小圈

Preface 前言

雙(三)倍經驗!

第三題的部落格qwq


Problem model 問題模型

這三道題都是給定一張有向圖,首先判斷是否有環

如果有環就求出最大環或最小環的平均值

解決這種題,我們通常都是使用 二分答案 & SPFA 判環求最短(長)路

Train of thought 思路點撥

  • 為什麼會想到二分答案呢?

因為在找最大(小)環的時候我們一般會想到:

遍歷整個圖,找到每一個環,然後算出它們的平均值,最後比較出最大(小)值

但是題目可能是多組資料,還不說圖的大小,所以這種思路大概率會 \(T\) 飛的

  • 那怎麼做?

——不能找環,那我們就直接找答案啊!(這裡要轉換一下思想)

因為找答案 \(=\) 列舉答案,而列舉答案一般使用較高效的二分答案

  • 再來轉換一下關鍵式子

我們能將求答案\(ans\)的式子表示如下:

\(ans=(len_1+len_2+len_3+···+len_k)/K\)

數學轉換一下:

\(ans*K=len_1+len_2+len_3+···+len_k\)

移項一下:

\((len_1+len_2+len_3+···+len_k)-ans*K=0\)

最後我們可得:

\((len_1-ans)+(len_2-ans)+(len_3-ans)+···+(len_k-ans)≥0\)

(因為最開始的式子是整除,會有精度問題,所以是 \(≥\)

  • 經過上述轉換之後,\(SPFA\)更新路徑長度的寫法就應該如下:

dis[v]>dis[now]+e[i].val-middis[v]<dis[now]+e[i].val-mid

dis[v]=dis[now]+e[i].val-mid

其中 \(mid\) 是我們當前列舉的答案(第一句是最小環,第二句是最大環)

  • \(check\) 函式的寫法也變得顯而易見:

每次枚舉了 \(ans\),就跑 \(SPFA\) ,如果存在環則說明當前列舉的 \(ans\) 是合法的,反之不合法


Code 程式碼

注:這三道題我都寫的 \(dfs\) 版的 \(SPFA\)

#include <bits/stdc++.h>
using namespace std;
int n,m,u,v,tot;
double w,dis[520010];
int vis[520010],head[520010];

struct node {
    int to,net;
    double val;
} e[520010];

inline void add(int u,int v,double w) {
    e[++tot].to=v;
    e[tot].val=w;
    e[tot].net=head[u];
    head[u]=tot;
}

inline bool dfs(int now,double x) {
    vis[now]=1;
    for(register int i=head[now];i;i=e[i].net) {
        int v=e[i].to;
        if(dis[v]>dis[now]+e[i].val-x) {
            dis[v]=dis[now]+e[i].val-x;
            if(vis[v]==1||dfs(v,x)==true) return true;
        }
    }
    vis[now]=0;
    return false;
}

inline bool check(double x) {
    for(register int i=1;i<=n;i++) {
        vis[i]=0;
        dis[i]=20050206;
    }
    for(register int i=1;i<=n;i++) {
        if(dfs(i,x)==true) return true;
    }
    return false;
}

int main() {
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=m;i++) {
        scanf("%d%d%lf",&u,&v,&w);
        add(u,v,w);
    }
    double l=-10000000,r=10000000;
    while(r-l>1e-10) {
        double mid=(l+r)/2;
        if(check(mid)==true) r=mid;
        else l=mid;
    }
    printf("%.8lf",l);
    return 0;
}

Epilogue 尾聲

最後,如果這篇題解有任何問題或不懂的地方,歡迎留言區評論

我一定會及時回覆、改正,謝謝各位dalao呀!orz