[HNOI2009]最小圈 (二分答案+负环)

题面:[HNOI2009]最小圈

题目描述:

考虑带权的有向图\(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\)中的一个圈当且仅当\((c_i,c_{i+1})(1\le i\lt k)\)\((c_k,c_1)\)都在\(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))\)

输入格式:

第一行2个正整数,分别为\(n\)\(m\),并用一个空格隔开,只用\(n=|V|,m=|E|\)分别表示图中有\(n\)个点\(m\)条边。
接下来m行,每行3个数\(i,j,w_{i,j}\),表示有一条边\((i,j)\)且该边的权值为\(w_{i,j}\)。输入数据保证图\(G=(V,E)\)连通,存在圈且有一个点能到达其他所有点。

输出格式:

请输出一个实数\(\mu'(c)=Min(\mu(c))\),要求输出到小数点后8位。

输入样例#1:

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

输出样例#1:

3.66666667

输入样例#2:

2 2
1 2 -2.9
2 1 -3.1

输出样例#2:

-3.00000000

说明:

对于100%的数据,\(n\le 3000,m\le 10000,|w_{i,j}| \le 10^7\)



\(solution:\)

这道题要我们求平均值的最小值,所以我们考虑二分答案的可能性,先列出答案的意义:

\(ans=\frac{\sum\limits_{i=1}^{k} w_{c_i,c_{i+1}}}{K}\quad _{(c_{k+1}=c_1)}\)

我们将它转换一下:

\(ans*k={\sum\limits_{i=1}^{k} w_{c_i,c_{i+1}}}\quad _{(c_{k+1}=c_1)}\)

\(0=\sum\limits_{i=1}^{k} (w_{c_i,c_{i+1}})-ans*k\quad _{(c_{k+1}=c_1)}\)

\(0=\sum\limits_{i=1}^{k}(w_{c_i,c_{i+1}}-ans)\quad _{(c_{k+1}=c_1)}\)

这样我们发现它已经化成了一个二分答案的常用等式(等式右边可以\(O(n)\)求出来,且具备单调性)而我们注意到等式左边为0,所以我们可以二分ans,并将边权改为\(w_{c_i,c_{i+1}}-mid\) ,然后求负环即可。

为什么可以这样做呢?这个较地震那一题好讲一些,我们当前二分出来的平均值mid,我们将每一条边的边权都减去它,如果存在负环,说明这个环上所有边权实际边权值加起来的平均值一定小于mid!(这里需要仔细想一下)



\(code:\)

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define inf 0x7fffffff
#define rg register int

using namespace std;

const db cha=1e-9;

struct su{
    db v;int to,next;
}a[10005];

bool f;
int n,m,top;
int tou[3005];
bool vis[3005];
db mid,dis[3005];

inline int qr(){
    char ch;
    while((ch=getchar())<'0'||ch>'9');
    int res=ch^48;
    while((ch=getchar())>='0'&&ch<='9')
        res=res*10+(ch^48);
    return res;
}

inline void add(int x,int y){
    scanf("%lf",&a[++top].v);
    a[top].to=y;
    a[top].next=tou[x];
    tou[x]=top;
}

inline void spfa(int i){
    vis[i]=1;
    for(rg j=tou[i];j;j=a[j].next){
        if(dis[a[j].to]>dis[i]+a[j].v-mid){
            dis[a[j].to]=dis[i]+a[j].v-mid;
            if(vis[a[j].to])return void(f=1);
            else spfa(a[j].to);
        }
    }vis[i]=0;
}

inline bool check(){
    for(rg i=1;i<=n;++i)
        dis[i]=vis[i]=0;;f=0;
    for(rg i=1;i<=n&&!f;++i)
        if(!vis[i])spfa(i);
    return f;
}

int main(){
    freopen("cycle.in","r",stdin);
    freopen("cycle.out","w",stdout);
    n=qr(),m=qr();
    for(rg i=1;i<=m;++i)
        add(qr(),qr());
    db l=-1e7,r=1e7;
    while(l<=r){
        mid=(l+r)/2;
        if(check())r=mid-cha;
        else l=mid+cha;
    }printf("%.8lf\n",l);
    return 0;
}

转载于:https://www.cnblogs.com/812-xiao-wen/p/10367943.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值