01分数规划,SPFA——POJ3621
题目含义
有一个有向图,每个点都有点权,每条边也都有边权
然后有头牛要从任一点出发,经过至少两个点后回到原点,即走一个环
问经过的sigma点权/sigma边权最大是多少
题目分析
最开始没想出来,看了网上说用SPFA判负环也有点不明白
因为不是说每个点的点权只得到一次吗,SPFA是怎么处理这点的?
然后想到,这是因为我在考虑排除原路返回的情况
因为原路返回没得到点权,反而增加了边权肯定是不划算的
但是SPFA判的负环根本不会原路返回啊!!!如果原路返回了,那还是环吗,那就是重叠边了呀
所以是我多虑了
——————————————————————
若sigmaPi/sigmaWi取得最大,那么任意一个答案ans必定<=sigmaPi/sigmaWi
转换一下就是 sigmaPi-sigmaWi*ans>=0 => sigma( Pi-ans*Wi )>=0
然后就是二分,如果ans满足这个式子,low=mid,否则high=mid
但是这个sigma(Pi-ans*Wi)怎么求呢
只能想到用SPFA,如果有负环就说明从某一起点走一圈回到这个起点时sigma边权<0
所以将边权换成我们的Pi-ans*Wi就可以做了
题目代码
初始化手写就能过,memset就会wa,不知道为什么......
玄学memset
#include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> #include<queue> using namespace std; typedef long long LL; const int maxm=5007; const int maxn=1007; const double eps=1e-7; struct edge{ int to,next,dis; }e[maxm]; int head[maxm],tot; void add(int u,int v,int w){ e[tot].dis=w; e[tot].to=v; e[tot].next=head[u]; head[u]=tot++; } int f[maxn],n,m,a,b,c; bool SPFA(double x){ double dis[maxn]; bool vis[maxn]; int num[maxn]; for(int i=1;i<=n;i++){ dis[i]=1e100; vis[i]=false; num[i]=0; } // memset(dis,1e100,sizeof(dis)); // memset(vis,false,sizeof(vis)); // memset(num,0,sizeof(num)); queue<int>q; q.push(1); dis[1]=0; vis[1]=true; num[1]++; while(!q.empty()){ int u=q.front();q.pop(); vis[u]=false; for(int i=head[u];i!=-1;i=e[i].next){ int v=e[i].to; if(dis[v]>dis[u]+x*e[i].dis-f[v]){ dis[v]=dis[u]+x*e[i].dis-f[v]; if(!vis[v]){ vis[v]=true; q.push(v); num[v]++; if(num[v]>n)return true; } } } } return false; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&f[i]); memset(head,-1,sizeof(head)); for(int i=1;i<=m;i++){ scanf("%d%d%d",&a,&b,&c); add(a,b,c); } double l=0.0,r=10000,mid; while(r-l>eps){ mid=(l+r)/2; if(SPFA(mid))l=mid; else r=mid; } printf("%.2f\n",mid); return 0; }