//题目类型:分数规划
//算法实现: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;
}