挖宝(二分答案+dp)
n个点,有m条边连接。小R从第一个点开始走,且必须从编号小的点走到编号大的点,直到走到第n个点。 每条边有时间\(t[i]\),价值\(w[i]\),求\(max(\frac{\sum w[i]}{\sum t[i]})\)。
先考虑直接dp出答案,怎么设计状态呢?我们发现分数是不能累加的,导致转移不了状态。所以可不可以通过枚举答案,然后验证解来解题呢?如果当前枚举的答案是A,说明\(\sum w[i]-\sum t[i]*A=0\)。于是这样,我们就把分数的状态转化成了整数,可以进行累加了。\(f[i]\)表示前i个点,\(\sum w[i]-\sum t[i]*A\)的最大值。k是i的后继点,那么\(f[k]=max(f[i]+w[k, i]-t[k, i]*A, f[k])\)。如果\(f[k]>0\),说明答案比A大,否则说明答案比A小。于是二分答案即可。
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e4+5, maxm=1e5+5, INF=1e9;
const double eps=1e-5;
class Graph{
public:
struct Edge{
int to, next, t, w; Graph *bel;
Edge& operator ++(){ return *this=bel->edge[next]; }
inline int operator *(){ return to; }
};
void addedge(int x, int y, int t, int w){
Edge &e=edge[++cntedge]; e.bel=this;
e.to=y; e.next=fir[x]; e.t=t; e.w=w;
fir[x]=cntedge;
}
Edge& getlink(int x){ return edge[fir[x]]; }
private:
int cntedge, fir[maxn];
Edge edge[maxm*2];
};
Graph g;
int n, m;
double f[maxn];
bool ok(double A){
fill(f, f+maxn, -INF);
f[1]=0; Graph::Edge e;
for (int i=1; i<=n; ++i){
e=g.getlink(i);
for (; *e; ++e){
if (*e<i) continue;
f[*e]=max(f[*e], f[i]+e.w-e.t*A);
}
}
return f[n]>0;
}
int main(){
scanf("%d%d", &n, &m);
int a, b, c, d; double l=0, r=1e5, mid;
for (int i=0; i<m; ++i){
scanf("%d%d%d%d", &a, &b, &c, &d);
if (a>b) swap(a, b);
g.addedge(a, b, c, d);
}
while (r-l>eps){
mid=(r+l)/2;
if (ok(mid)) l=mid; else r=mid;
}
printf("%.4lf", mid);
return 0;
}