[BZOJ 1486][HNOI2009]最小圈

33 篇文章 0 订阅

AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=1486


最小圈


Description

考虑带权的有向图 G=VE 以及 wE –> R
每条边 e=ijijiVjV 的权值定义为 w[i][j] ,令 n=|V|
c=c[1]c[2]c[k]c[i]V G 中的一个圈当且仅当c[i]c[i+1]1ik c[k]c[i] 都在 E 中。
这时称k为圈 c 的长度同时令c[k+1]=c[1],并定义圈 c=c[1]c[2]c[k] 的平均值为 μc=sumki=1w[ci][ci+1]/k ,即c上所有边的权值的平均值。
μc=Min{μc} G 中所有c的平均值的最小值。
现在的目标是:在给定了一个图 G=VE 以及 wE –> R 之后,请求出G中所有圈 c 的平均值的最小值μc=Min{μc}


Input

从文件 input.txt 中读入数据,文件中第一行包含两个整数n和m,并用一个空格隔开,其中 n=|V|m=|E| 分别表示图中有 n 个点和m条边。接下来m行,每行包含用空格隔开的 3 个数ij w[i][j] ,表示有一条边 ij 且该边的权值为 w[i][j] 。输入数据保证图 G=VE 连通,存在圈且有一个点能到达其他所有点。


Output

输出文件 output.txt 中仅包含一个实数 μc=Minμc ,要求输出到小数点后8位。


Sample Input 1

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


Sample Output 1

3.66666667


Sample Input 2

2 2
1 2 -2.9
2 1 -3.1


Sample Output 2

-3.00000000


HINT

样例1中共有2个圈(1,2,3)和(1,2,4)。其中第一个圈的平均值为5,第二个圈的平均值为11/3。样例2中存在一个负圈。

20%的数据:n≤100,m≤1000;
50%的数据:n≤1000,m≤5000;
100%的数据:n≤3000,m≤10000;
100%的数据:|w[i][j]|≤10^7。


Solution

这道题时一道分数规划的题目。先二分答案。

若ans1>ans,则有:
ans1>sumki=1w[c[i]][c[i]+1]/k=>sumki=1(ans1w[c[i]][c[i]+1])>0
sumki=1(w[c[i]][c[i]+1]ans1)<0
所以只要将所有边的权值全部减去ans1,若此时图中仍存在负环,那么ans就还可以更小。

若ans1≤ans,同理,
sumki=1(w[c[i]][c[i]+1]ans1)0
所以如果执行完上面的操作,发现图中不存在负环,那么ans就不能更小了。


Code

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int head[3010],nxt[10010],data[10010];
double wei[10010],dis[3010];

bool vis[3010],flag=false;
int n,m,cnt=1;
double l=-10000000,r=10000000;
int num[10010][10];
double ans=10000001;

void add(int x,int y,double z){
    nxt[cnt]=head[x];data[cnt]=y;wei[cnt]=z;head[x]=cnt++;
}

void dfs(int now){
    vis[now]=true;
    for(int i=head[now];i;i=nxt[i]){
        if(dis[now]+wei[i]<dis[data[i]]){
            if(vis[data[i]]){flag=true;break;}
            dis[data[i]]=dis[now]+wei[i];
            dfs(data[i]);
        }
    }
    vis[now]=false;
}

bool pan(double now){
    memset(head,0,sizeof head);memset(nxt,0,sizeof nxt);
    memset(data,0,sizeof data);memset(wei,0,sizeof wei);
    cnt=1;
    for(int i=1;i<=m;i++)add(num[i][1],num[i][2],num[i][3]-now);
    for(int i=1;i<=n;i++){
        memset(dis,50,sizeof dis);
        flag=false;
        dis[i]=0;
        dfs(i);
        if(flag)return true;
    }
    return false;
}

int main(){
    freopen("cycle.in","r",stdin);
    freopen("cycle.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&num[i][1],&num[i][2],&num[i][3]);
    while(r-l>=1e-10){
        double mid=(r+l)/2;
        bool flag=pan(mid);
        if(flag){
            if(ans>mid)ans=mid;
            if(r==mid)break;
            r=mid;
        }
        else{
            if(l==mid)break;
            l=mid;
        }
    }
    printf("%.8lf",ans);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值