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;
}