POJ 3621

//题目类型:分数规划
//算法实现:SPFA+二分法
题意:求存在一个环路,所有的点权之和/所以的边权之和 最大是多少?
算法:此题是对01分数规划的应用,那么首先明白01分数规划的思想.
01分数规划的思想的描述如下:令c=(c1,c2,…,cn)和d=(d1,d2,…,dn)为n维整数向量,那么一个0-1分数规划问题用公式描述如下:FP:                                                                                                        
最小化(c1x1+…cnxn)/(d1x1…dnxn)=cx/dx xi∈{0,1}这里x表示列向量(x1,x2,…,xn)T .0-1值向量的子集Ω称作可行域,而x则是Ω的
一个元素,我们称x为可行解。即可以简化为y=c/d.那么再演变一下:y-c/d=0.我们目标是求y.那么我们可以假设函数f(y)=y-c/d.
重要结论:
对于分数规划问题,有许多算法都能利用下面的线性目标函数解决问题。
Q(L): 最小化 cx-Ldx xi∈{0,1}
记z(L)为Q(L)的最值。令x*为分数规划的最优解,并且令L*=(cx*)/(dx*)(注:分数规划的最值)。那么下面就容易知道了:
Q(L) > 0 当且仅当 L<L*
Q(L) = 0 当且仅当 L=L*
Q(L) < 0 当且仅当 L>L*
此外,Q(L*)的最优解也能使分数规划最优化。因此,解决分数规划问题在本质上等同于寻找L=L*使Q(L)=0
因此,求解f(y)=0,为其函数的最优解,即可以利用二分的思想逐步推演y,从而求得最优解.
回到题目,我们知道是求解segma(f[V])/segma(E[v])的最大值,同时每个结点对应一个点权,每条边对应一个边权,那么我们就可以联想到应
用01分数规划的思想来求解.而01分数规划是与二分紧紧联系在一起的.那么怎么应用二分求解呢?
我们首先想想当仅仅有2个结点环路的时候,问题就演变为f(y)=y-fun/time,而y是通过二分逐步推算出来的,那么我们的任务就变为在一定的精度
范围内测试求解其最优解.考虑到函数y-fun/time是关于y的增函数,所以当y-fun/time>0时,y减少; y-fun/time<0时,y增大.在2个结点之间,那么
我们就可用重新将图的权变为y-fun/time,这样问题就回到2个结点的环路是否存在负权回路,存在说明y-fun/time<0,不存在y-fun/time>0.从而进
一步推算最优解y。
解题思路参照了此位牛人:http://blog.csdn.net/find_my_dream/archive/2009/11/14/4810489.aspx
#include <iostream>
#include <queue>
#include <math.h>
//#include <conio.h>
#include <vector>
using namespace std;
#define narray 1001
typedef struct edge
{
     int v;
     int w;
}edge;
int n,m;
const double maxData = 10000000000.0;
vector<edge> adj[narray];
int fun[narray];
int cnt[narray];
bool final[narray];
double d[narray];
int src,des;
bool SPFA(double mid)
{
     int i,j;
     queue<int> myqueue;
     for(i=1;i<=n;++i) d[i] = maxData;
     d[src]=0;
     memset(cnt,0,sizeof(cnt));
     memset(final,0,sizeof(final));
     cnt[src]++;
     final[src] = true;
     myqueue.push(src);
     while(!myqueue.empty())
     {
         int frontv = myqueue.front();
         final[frontv] = false;
         myqueue.pop();
         for(j=0;j<adj[frontv].size();++j)
         {
             int tempv = adj[frontv][j].v;
             int tempw = adj[frontv][j].w;
             if(d[tempv]>d[frontv]+tempw*mid-fun[tempv])      //权值为time*ans-fun
             {
                 d[tempv] = d[frontv]+tempw*mid-fun[tempv];
                 if(!final[tempv])
                 {
                     myqueue.push(tempv);
                     cnt[tempv]++;
                     if(cnt[tempv]>=n) return false;   //有负权回路
                 }                 
             }
         }
     }
     return true;   //没有负权回路
}
int main()
{
    //freopen("1.txt","r",stdin);
    int i,j;
    int start,end;
    int weight;
    double low,high,mid,ans;
    edge tempedge;
    while(scanf("%d%d",&n,&m)!=-1)
    {
         for(i=1;i<=n;++i)
         {
              adj[i].clear();
              scanf("%d",&fun[i]);
         }
         for(i=1;i<=m;++i)
         {
              scanf("%d%d%d",&start,&end,&weight);
              tempedge.v = end;
              tempedge.w = weight;
              adj[start].push_back(tempedge);
         }
         low = 0;
         high = 1000;      //1<=Ti<=1000,1<=Fi<=1000,回路中包含一个顶点时最大值为1000,二个顶点时最大值是1000,依次递推,最大值为1000
         src = 1;
         /*for(i=0;i<=20;++i)
         {
              mid = (low+high)/2;
              if(SPFA(mid))      //没有负权环
                  high = mid;
              else
                  low = mid;                 
         }*/
         while(1)           //这种方法比较常见,采用for循环对于此题亦可
         {
              ans = mid;
              mid = (low+high)/2;
              if(fabs(ans-mid)<1e-6)          //此处将精度设置为1e-6,如果精度过低如1e-3,则会出错
                  break;
              if(SPFA(mid))      //没有负权环
                  high = mid;    //由于都是double类型,所以直接赋值
              else
                  low = mid;  
         }
         printf("%.2lf\n",ans);
    }
    //getch();
    return 0;
}

你可能感兴趣的:(poj)