poj 3621(参数搜索 + spfa)

01整数规划问题就是求解方程(a1*x1+a2*x2+..+an*xn)/(b1*x1+b2*x2+..+bn*xn)的最小值/最大值问题。其中xi = 0或1(i=1,2...n)

对于此类问题我们可以通过二分来求解很接近答案的近似值。我们可以先令:
(a1*x1+a2*x2+..+an*xn)/(b1*x1+b2*x2+..+bn*xn)=L,则我们可以将此式转换为:x1*(a1-b1*L)+x2*(a2-b2*L)+...xn*(an-bn*L)=0,我们先定义一个估计值val,如果这个值使得上面的式子小于0我们就可以知道val>L,如果上式等于0,则val = L;如果大于0,则val<L,显然我们可以采用二分的思想求解次问题。

对于POJ 3621 假设存在边uv,点u和点v的欢乐值分别为happy[u]和happy[v]。u到v的花费为cost[u][v]。则我们可以构造一个新图,这个新图的边变为:
happy[v]-val*cost[u][v](val为估计值),然后我们采用SPFA求解此图是否存在负环。如果存在负环。假设此环所有点为y1,y2.....ym,则满足下面式子:
(cost[1][2]*val-happ[2])+(cost[2][3]*val-happy[3])+....+(cost[m][1]-happ[1]) < 0
显然val比最优解还要小,此时我们可以增大val,反之,如果不存在负环,则我们需要减少val(对于a1和b1*L在符号中的先后关系需要看题目是求最大值还是最小值,此题是把b1*L放在符号前面的)。如此进行二分求解,知道满足题目要求的精度就可以终止了。 对于POJ 2728 则可以使用类似的方法求解.

 讲解转自 http://hi.baidu.com/ofeitian/blog/item/ee15253e4f0f4dce7c1e7123.html 


#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const double inf=1000000000000.00;
const int N=5010,M=1010;
#define cc(m,v) memset(m,v,sizeof(m))
struct node{
    int v,next,w;
}edge[N];
int head[M],p,vis[M],num[M],sum[M];
double dis[M];
queue<int> que;
void ainit(){
    p=0,cc(head,-1);
}
void addedge(int u,int v,int w){
    edge[p].v=v,edge[p].w=w,edge[p].next=head[u],head[u]=p++;
}
bool spfa(double val,int n){
    int i,u,v;
    double w;
    for(i=0;i<=n;i++)  dis[i]=inf;
    cc(vis,0),cc(sum,0);
    que.push(1),vis[1]=1,dis[1]=0;
    while(!que.empty()){
        u=que.front(),que.pop();
        vis[u]=0;
        for(i=head[u];i!=-1;i=edge[i].next){
            w=(double)val*edge[i].w-num[v=edge[i].v];
            if(dis[v]>w+dis[u]){
                dis[v]=w+dis[u];
                sum[v]++;
                if(sum[v]>=n) return 0;
                if(!vis[v])
                    que.push(v),vis[v]=1;
            }
        }
    }
    return 1;
}
int main(){
    int n,m,u,v,i,w;
    double rig,lef,mid,ans;
    while(scanf("%d%d",&n,&m)!=-1){
        ainit();
        for(i=1;i<=n;i++)
            scanf("%d",&num[i]);
        while(m--){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }
        lef=0,rig=1000;
        while(rig-lef>0.0001){
            mid=(lef+rig)/2;
            if(spfa(mid,n)) ans=mid,rig=mid;
            else lef=mid;
        }
        printf("%.2lf\n",ans);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值