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 biaimid
  • 然后通过移项, ∑ a i ≥ m i d × ∑ b i \sum a_i≥mid \times\sum b_i aimid×bi
  • 又有 ∑ a i − m i d × b i ≥ 0 \sum a_i-mid\times b_i≥0 aimid×bi0
  • 那么令 i i i号点的贡献为 w i = a i − m i d × b i w_i=a_i-mid\times b_i wi=aimid×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 jiL 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]] cic[q[r]]时, [ q [ r − 1 ] , q [ r ] ) [q[r-1],q[r]) [q[r1],q[r])区间内的 x x x要从 q [ r ] q[r] q[r]变成 i i i,于是用线段树区间修改在这个区间加上 w i − w q [ r ] w_i-w_{q[r]} wiwq[r]
  • 接着队尾加入当前点 i i i,线段树中加入点 i − 1 i-1 i1值为 f i − 1 + w i f_{i-1}+w_i fi1+wi
  • 然后再区间查询线段树 [ m a x ( i − L , 0 ) , i − 1 ] [max(i-L,0),i-1] [max(iL,0),i1]中的最大值,更新 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;
}
posted @ 2020-02-10 21:48  AnAn_119  阅读(89)  评论(0编辑  收藏  举报