poj3621 0/1分数规划

解题报告

题目:http://poj.org/problem?id=3621

题目大意:在一个有向图中,求一个环,使得这个环上所有点的权值之和比所有边权之和最大,输出比值。

思路:想了很久,但都没能转化成二分答案,判环的思想,最后还是看了别人的思路。

0/1分数规划问题。就此题而言,我们要求的是

V[i] / C[i] = ans;

设一个数k

k<=ans V[i] / C[i] >= k.V[i] – k * C[i] >= 0.

k> ans V[i] / C[i] < k V[i] – k * C[i] < 0;

根据上面两个等式:如果我们以V[i] – k * C[i]来代替图中原来的边,那么:

k<=ans 时,图中就会出现正环,这是我们增大k,当没有正环时,减小k。这样二分便可。但是这里还是有一个问题,就是“当k> ans V[i] / C[i] < k V[i] – k * C[i] < 0;

”,那么我们为什么不判当出现负环的时候减小k呢,而没有负环的时候增大k呢?仔细想想,只要有某个环的V[i] / C[i]的值很小,那么一个没达到ansk值就会导致他变成负环。但是这个k却里最终ans还很远。而出现只要k小于最终的ans,那么对于比例最大的那个环,他必然会从出现正环,这才是我们为什么用正环的原因所在,想用负环,只要将边值k * C[i] - V[i]便可。

 

收获与经验:刚过此题是并没有理解到这些,是在写解题报告是才把正负环搞清楚的。。以后要经常写解题报告了。

 

AC code

#include <cstdio>

#include <cstring>

#include <cmath>

 

#define MAXN 2010

#define MAXM 10010

#define EXP 1e-6

#define INF 21000000

 

struct NODE{

         int to, len, next;

};

NODE edges[MAXM];

int head[MAXN], gain[MAXN], ad;

 

void clear(){

         ad = 0;

         memset(head, -1, sizeof(head));

}

 

void insert(int u, int v, int len){

         edges[ad].to = v, edges[ad].len = len, edges[ad].next = head[u], head[u] = ad ++;

}

 

 

int in[MAXN], times[MAXN], stack[MAXN];

double dis[MAXN];

bool SPFA(double k, int n){

         int top = -1, u, v, len;

         memset(in, 0, sizeof(in));

         memset(times, 0, sizeof(times));

         for(int i = 1; i <= n; ++ i){

                   dis[i] = 0;

                   in[i] = times[i] = 1;

                   stack[++top] = i;

         }

         while(top >= 0){

                   u = stack[top --];

                   in[u] = 0;

                   for(int p = head[u]; ~p; p = edges[p].next){

                            v = edges[p].to, len = edges[p].len;

                            if(dis[v] > dis[u] - gain[v] + k * len){

                                     dis[v] = dis[u] - gain[v] + k * len;

                                     if(!in[v]){

                                               in[v] = 1, times[v] ++;

                                               if(times[v] > n) return 0;

                                               stack[++ top] = v;

                                     }

                            }

                   }

         }

         return 1;

}

 

double slove(int n, int m, int up){

         double l = 0, r = up, mid, ans = 0;

         while(r - l > EXP){

                   mid = (l + r) / 2;

                   if(fabs(ans - mid) < EXP) return ans;

                   ans = mid;

                   if(SPFA(mid, n)) r = mid;

                   else l = mid;

         }

         return 0;

}

 

int main(){

         int n, m,x, y, z, up;

         while(~scanf("%d %d", &n, &m)){

                   clear();

                   up = 0;

                   for(int i = 1; i <= n; ++ i){

                            scanf("%d", &gain[i]);

                            up += gain[i];

                   }

                   for(int i = 1; i <= n; ++ i) insert(0, i, 0);

                   for(int i = 0; i < m; ++ i){

                            scanf("%d %d %d", &x, &y, &z);

                            insert(x, y, z);

                   }

                   double ans = slove(n, m, up);

                   if(ans > 0) printf("%.2lf\n", ans);

                   else printf("0\n");

         }

         return 0;

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值