1. 程式人生 > >BZOJ1486 最小圈 [分數規劃+負權環]

BZOJ1486 最小圈 [分數規劃+負權環]

pan 前向星 c++ 思想 https limit head for 以及

Description

考慮帶權的有向圖\(G=(V,E)\)以及\(w:E\rightarrow R\),每條邊\(e=(i,j)(i\neq j,i\in V,j\in V)\)的權
值定義為\(w_{i,j}\),令\(n=|V|\)\(c=(c_1,c_2,\cdots,c_k)(c_i\in V)\)\(G\)中的一個圈當且僅當
\((ci,ci+1)(1≤i<k)\)\((ck,c1)\)都在\(E\)中,這時稱\(k\)為圈\(c\)的長度同時令\(c_{k+1}=c_1\),並定義圈\(c=(c_1,c_2,\cdots,c_k)\)的平均值為\(\mu(c)=\sum\limits_{i=1}^{k} w_{c_i,c_{i+1}}/k\)

,即\(c\)上所有邊的權值的平均值。令\(\mu‘(c)=Min(\mu(c))\)\(G\)中所有圈\(c\)的平均值的最小值。現在的目標是:在給定了一個圖\(G=(V,E)\)以及\(w:E\rightarrow R\)之後,請求出\(G\)中所有圈\(c\)的平均值的最小值\(\mu‘(c)=Min(\mu(c))\)

Input

第一行2個正整數,分別為\(n\)\(m\),並用一個空格隔開,只用\(n=|V|,m=|E|\)分別表示圖中有\(n\)個點\(m\)條邊。 接下來m行,每行3個數\(i,j,w_{i,j}\),表示有一條邊\((i,j)\)且該邊的權值為\(w_{i,j}\)

。輸入數據保證圖\(G=(V,E)\)連通,存在圈且有一個點能到達其他所有點。

Output

請輸出一個實數\(\mu‘(c)=Min(\mu(c))\),要求輸出到小數點後8位。

Sample Input

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

Sample Output

3.66666667

Hint

對於100%的數據,\(n\le 3000,m\le 10000,|w_{i,j}| \le 10^7\)

思路

看到題目中的“平均值的最小值”,便想到可以分數規劃,這道題運用的是0-1分數規劃的思想。

0-1分數規劃的一般形式是這樣一個式子:
\(r=(∑(c_i*x_i))/(∑(d_i*x_i))\)

在其中 \(x_i∈\){0,1}

我們一般求的便是r的最值。有一種基礎的方法是二分r的值:
將原式變形,\(∑(c_i*x_i))-(∑(d_i*x_i)*r=0\)
\(f(r)=∑(c_i*x_i))-(∑(d_i*x_i)*r\)

不難看出\(f(r)\)是單調遞減的函數,我們可以通過\(f(r)\)與0的關系來二分出r的最值:
\(f(r)>0\)時 表明這時r可以取更大
\(f(r)=0\)時 表明這時r即為符合要求的最值
\(f(r)<0\)時 表明這時r可以取更小

我們便在本題中運用這個思想。在本題中,公式中的r即為最小的平均值,c即為邊權,d為1 (∑\(d_i*x_i\)即為邊的個數),x表示這條邊是否在圈中,f(r)轉換後可以看作是邊權減去r後最小圈的權值之和。

當f(r)<0時,這個圈就是負權環,於是我們便可以根據是否存在負權環來二分出r的值。
我判斷負權環的方法是使用dfs-spfa,詳細內容可以看代碼(順便給出模板題鏈接洛谷P3385)。

代碼

需要註意的細節:

  1. 註意二分的精度
  2. 判負環註意方法,不然容易T
#include <bits/stdc++.h>
#define exp 1e-9  //二分的精度
#define inf 1e9
#define MAXN 10005
using namespace std;
int n,m,a[MAXN],b[MAXN];
int cnt,head[MAXN],vis[MAXN],flag;
double dis[MAXN],c[MAXN]; //一定要用double存
struct Edge{int to,next; double w;} edge[MAXN];

void addedge(int x, int y, double w)
{ //前向星存圖
    edge[++cnt].next=head[x];
    edge[cnt].to=y;
    edge[cnt].w=w;
    head[x]=cnt;
}

void spfa(int x)
{  //dfs_spfa判負環
    vis[x]=true;
    for(int i=head[x]; i; i=edge[i].next)
    {
        int to=edge[i].to;
        if(dis[x]+edge[i].w<dis[to])
        {
            dis[to]=dis[x]+edge[i].w;
            if(vis[to]) {flag=true; return;}
            else spfa(to);
        }
    }
    vis[x]=false;
}

bool check(double x)
{
    cnt=flag=0;
    memset(head, 0, sizeof(head));
    memset(dis, 127/3, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    for(int i=1; i<=m; i++) addedge(a[i], b[i], c[i]-x); //重新賦權值
    for(int i=1; i<=n; i++)
    {
        spfa(i); //判負環
        if(flag) return 1;  
    }
    return 0;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++) scanf("%d%d%lf",&a[i],&b[i],&c[i]);
    double l=-inf,r=inf;
    while(l+exp<r) //分數規劃
    {
        double mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid;
    }
    printf("%.8lf",l);
    return 0;
} 

BZOJ1486 最小圈 [分數規劃+負權環]