pku 3621 sightseeing cows 解题报告
题意:求存在一个环路,所有的点权之和/所以的边权之和 最大是多少?
算法:此题是对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*)(注:分数规划的最值)。那么下面就容易知道了:
z(L) > 0 当且仅当 L<L*
z(L) = 0 当且仅当 L=L*
z(L) < 0 当且仅当 L>L*
此外,Q(L*)的最优解也能使分数规划最优化。因此,解决分数规划问题在本质上等同于寻找L=L*使z(L)=0
因此,求解f(y)=0,为其函数的最优解,即可以利用二分的思想逐步推演y,从而求得最优解.
回到题目,我们知道是求解segma(f[V])/segma(E[v])的最大值,同时每个结点对应一个点权,每条边对应一个边权,那么我们就可以联想到应用01分数规划的思想来求解.而01分数规划是与二分紧紧联系在一起的.那么怎么应用二分求解呢?
我们首先想想当仅仅有2个结点环路的时候,问题就演变为f(y)=y-c/d,而y是通过二分逐步推算出来的,那么我们的任务就变为在一定的精度范围内测试求解其最优解.当y-c/d>0时,y减少; y-c/d<0时,y增大.在2个结点之间,那么我们就可用重新将图的权变为y-c/d,这样问题就回到2个结点的环路是否存在负权回路,存在说明y-c/d<0,不存在y-c/d>0.从而进一步推算最优解y。
AC代码:
#include <stdio.h>
#include <string.h>
#define M 1005
const double inf = 10000000000;
int l, p;
struct node
{
int v, weight, next;
}graph[5005];
int pre[M], queue[M*M], count[M], f[M];
int spfa(double mid)
{
int i, front, tail;
double new_weight, dis[M];
bool visit[M];
memset(visit, 0, sizeof(visit));
memset(count, 0, sizeof(count));
for (i = 1; i <= l; i++)
{
dis[i] = inf;
}
front = 0; tail = 1;
queue[front] = 1; visit[1] = true; dis[1] = 0;
while (tail > front)
{
int t = queue[front++];
visit[t] = false;
for (i = pre[t]; i != -1; i = graph[i].next)
{
new_weight = graph[i].weight * mid - f[graph[i].v];
if (dis[graph[i].v] > dis[t] + new_weight)
{
dis[graph[i].v] = dis[t] + new_weight;
if (!visit[graph[i].v])
{
visit[graph[i].v] = 1;
queue[tail++] = graph[i].v;
count[graph[i].v]++;
//有负权环的时候,路径将不断的减小,那么count一定大于所有的结点之和
if (count[graph[i].v] > l)
{
return 1;
}
}
}
}
}
//没有负权环
return 0;
}
void init()
{
int i, a, b, c, top;
for (i = 1; i <= l; i++)
{
scanf("%d", &f[i]);
}
top = 0;
memset(pre, -1, sizeof(pre));
for (i = 0; i < p; i++)
{
scanf("%d%d%d", &a, &b, &c);
graph[top].v = b;
graph[top].weight = c;
graph[top].next = pre[a];
pre[a] = top++;
//printf("%d %d %d %d/n", graph[top - 1].v, graph[top - 1].next, graph[top - 1].weight, head[top - 1]);
}
}
int main()
{
freopen("1.txt", "r", stdin);
while (scanf("%d%d", &l, &p) != EOF)
{
init();
//01规划,求最优值mid
double left = 0, right = 100;
//精度要求不高
for (int i = 0; i <= 20; i++)
{
double mid = (left + right) / 2;
//01规划:E * mid - f;其中最优解为E*mid-f=0的时候
//当有负权环的时候,即E * mid - f<0,mid需要增大,反之需要减小
if (spfa(mid))
{
left = mid;
}
else
{
right = mid;
}
}
printf("%.2lf/n", left);
}
return 0;
}