JZOJ 4017. 【雅礼联考DAY01】逃跑(0/1分数规划+单调队列+线段树优化DP)
JZOJ 4017. 【雅礼联考DAY01】逃跑
题目
Description
Konrad, Delfador 和 Kalenz 一行人又喜闻乐见地被追杀了。
他们面临的是一条有 N 个地点的路, 他们从 0 号地点出发, 要逃到 N 号地点去。每个地点的战斗都有一定的金币收入 Ai,也有一定的部队损失 Bi。
为了更好地逃生, Delfador 还弄到了一块传送宝石,这样一行人就能向后传送不超过 L 的距离。从一个地点传送到另一个地点时,Konrad 会选择路径上除起点外的地形指数 Ci 最大的地点进行战斗,地形指数相同时选择最靠后的。
作为优秀的领导者, Konrad 希望总金币收入与总部队损失的比值最大。
Input
第一行,两个整数 N, L。
接下来 N 行,每行两个整数,分别表示 Ai, Bi, Ci。
Output
一行,一个实数,表示答案。
答案请使用科学计数法输出,保留 9 位小数,具体参见输出样例。指数为 0 时,最后应当输出’0.000000000e+000’。
Sample Input
5 3
1 1 1
1 2 2
2 3 1
1 9 2
1 1 1
Sample Output
3.750000000e-001
Data Constraint
题解
- 这题求比值最大,显然是0/1分数规划,一般的思路就是二分答案,
- 二分一个值 m i d mid mid,判断其是否可行,也就是需要 ∑ a i ∑ b i ≥ m i d \frac{\sum a_i}{\sum b_i}≥mid ∑bi∑ai≥mid,
- 然后通过移项, ∑ a i ≥ m i d × ∑ b i \sum a_i≥mid \times\sum b_i ∑ai≥mid×∑bi
- 又有 ∑ a i − m i d × b i ≥ 0 \sum a_i-mid\times b_i≥0 ∑ai−mid×bi≥0,
- 那么令 i i i号点的贡献为 w i = a i − m i d × b i w_i=a_i-mid\times b_i wi=ai−mid×bi,则可以通过简单的DP得到50分。
- 设 f i f_i fi表示到第 i i i号点的最大权值 w w w之和,那么 f i = m a x ( f j + w x ) f_i=max(f_j+w_x) fi=max(fj+wx),其中 j ≥ i − L j≥i-L j≥i−L且 x x x为 ( j , i ] (j,i] (j,i]中 c c c值最大的点,最后看 f n f_n fn是否不小于 0 0 0即可。
- 但这样的时间复杂度是 O ( n 2 log n ) O(n^2\log n) O(n2logn)的,需要优化。
- 考虑用一个单调队列维护地形指数 c c c最大的点,线段数记录每个点的 f i + w x f_i+w_x fi+wx, x x x的含义和上面一样,因为 x x x是会变化的,所以我们正是用单调对列维护 f i + w x f_i+w_x fi+wx值的改变。
- 每次判断 c i ≥ c [ q [ r ] ] c_i≥c[q[r]] ci≥c[q[r]]时, [ q [ r − 1 ] , q [ r ] ) [q[r-1],q[r]) [q[r−1],q[r])区间内的 x x x要从 q [ r ] q[r] q[r]变成 i i i,于是用线段树区间修改在这个区间加上 w i − w q [ r ] w_i-w_{q[r]} wi−wq[r],
- 接着队尾加入当前点 i i i,线段树中加入点 i − 1 i-1 i−1值为 f i − 1 + w i f_{i-1}+w_i fi−1+wi,
- 然后再区间查询线段树 [ m a x ( i − L , 0 ) , i − 1 ] [max(i-L,0),i-1] [max(i−L,0),i−1]中的最大值,更新 f i f_i fi。
- 注意用long double可能会时超,要改double。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ld double
#define e 0.0000000001
#define N 30010
ld f[N];
struct
{
ld a,b;
int c;
}a[N];
int n,L,q[N];
ld g[N*4],bz[N*4],w[N];
void add(int v,int l,int r,int x,int y,ld c)
{
if(l==x&&r==y)
{
g[v]+=c;
bz[v]+=c;
}
else
{
bz[v*2]+=bz[v],bz[v*2+1]+=bz[v];
g[v*2]+=bz[v],g[v*2+1]+=bz[v];
bz[v]=0;
int mid=(l+r)/2;
if(y<=mid) add(v*2,l,mid,x,y,c);
else if(x>mid) add(v*2+1,mid+1,r,x,y,c);
else
{
add(v*2,l,mid,x,mid,c);
add(v*2+1,mid+1,r,mid+1,y,c);
}
g[v]=max(g[v*2],g[v*2+1]);
}
}
ld find(int v,int l,int r,int x,int y)
{
if(l==x&&r==y) return g[v];
bz[v*2]+=bz[v],bz[v*2+1]+=bz[v];
g[v*2]+=bz[v],g[v*2+1]+=bz[v];
bz[v]=0;
int mid=(l+r)/2;
if(y<=mid) return find(v*2,l,mid,x,y);
else if(x>mid) return find(v*2+1,mid+1,r,x,y);
else return max(find(v*2,l,mid,x,mid),find(v*2+1,mid+1,r,mid+1,y));
}
int check(ld x)
{
memset(g,0,sizeof(g));
memset(bz,0,sizeof(bz));
for(int i=1;i<=n;i++) f[i]=-99999999999,w[i]=a[i].a-a[i].b*x;
f[0]=0;
int le=1,ri=0;
for(int i=1;i<=n;i++)
{
while(le<=ri&&a[i].c>=a[q[ri]].c)
{
add(1,0,n,q[ri-1],q[ri]-1,w[i]-w[q[ri]]);
ri--;
}
q[++ri]=i;
add(1,0,n,i-1,i-1,f[i-1]+w[i]);
f[i]=find(1,0,n,max(0,i-L),i-1);
}
return f[n]>=0;
}
int main()
{
int i;
scanf("%d%d",&n,&L);
for(i=1;i<=n;i++) scanf("%lf%lf%d",&a[i].a,&a[i].b,&a[i].c);
ld l=0,r=1000000,ans;
while(l+e<=r)
{
ld mid=(l+r)/2;
if(check(mid))
{
ans=mid;
l=mid+e;
}
else r=mid-e;
}
if(ans>=1&&ans<10)
{
printf("%.9lfe+000",ans);
}
else if(ans<1)
{
int ls=0;
while(ans+0.00000001<1) ans*=10,ls++;
printf("%.9lfe-",ans);
if(ls<10) printf("00%d",ls);
else if(ls<100) printf("0%d",ls);
else printf("%d",ls);
}
else
{
int ls=0;
while(ans+e>=10) ans/=10,ls++;
printf("%.9lfe+",ans);
if(ls<10) printf("00%d",ls);
else if(ls<100) printf("0%d",ls);
else printf("%d",ls);
}
return 0;
}
哈哈哈哈哈哈哈哈哈哈