bzoj 2131: 免费的馅饼(树状数组+DP)

2131: 免费的馅饼

Time Limit: 10 Sec   Memory Limit: 259 MB
Submit: 408   Solved: 245
[ Submit][ Status][ Discuss]

Description

Input

第一行是用空格隔开的二个正整数,分别给出了舞台的宽度W(1到10^8之间)和馅饼的个数n(1到10^5)。  接下来n行,每一行给出了一块馅饼的信息。由三个正整数组成,分别表示了每个馅饼落到舞台上的时刻t[i](1到10^8秒),掉到舞台上的格子的编号p[i](1和w之间),以及分值v[i](1到1000之间)。游戏开始时刻为0。输入文件中同一行相邻两项之间用一个空格隔开。输入数据中可能存在两个馅饼的t[i]和p[i]都一样。

Output

一个数,表示游戏者获得的最大总得分。

Sample Input

3 4
1 2 3
5 2 3
6 3 4
1 1 5

Sample Output

12


记得这道题有个简单版的,非常经典,可以直接O(WT)的暴力(W是宽度,T是最后一个馅饼掉下来的时间)

但是这题W和T都非常大,肯定不行

……

val[i]表示第i个馅饼的分值,loc[i]表示位置,t[i]表示时间

考虑O(n²)的DP, dp[i]表示接住第i个馅饼之后能取得的最大分数

那么dp[i] = max(dp[j]   (|loc[i]-loc[j]|<=2*(t[i]-t[j])))+val[i]

非常简单,但是n=100000还是会超时

……

其实这种取前驱最大值的dp,一般都可以树状数组维护,这样复杂度就做到O(nlogn)了

由上面的转移方程可得,接完x馅饼之后能接到y馅饼的条件是|loc[y]-loc[x]|<=2*(t[y]-t[x])

也就是loc[y]-loc[x]<=2*(t[y]-t[x]) && loc[x]-loc[y]<=2*(t[y]-t[x])

移项得2t[x]+p[x]<=2t[y]+p[y] && 2t[x]-p[x]<=2t[y]-p[y]

令a[x] = 2t[x]+p[x],  b[x] = 2t[x]-p[x]

这样的话就相当于对于馅饼x和y,如果a[x]<=a[y] && b[x]<=b[y]那么接完x馅饼之后能接到y馅饼,否则不行

这样就可以轻松用树状数组维护前驱最大值

按b从小到大遍历所有馅饼,对于第i个馅饼求出<=a[i]的前缀最大值并更新即可

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef struct Point
{
	int x, y;
	int val;
	bool operator < (const Point &b) const
	{
		if(y<b.y || y==b.y && x<b.x)
			return 1;
		return 0;
	}
}Point;
Point s[100005];
int cnt, Hash[100005], tre[100005];
void Update(int k, int val)
{
	while(k<=cnt)
	{
		tre[k] = max(val, tre[k]);
		k += k&-k;
	}
}
int Query(int k)
{
	int ans = 0;
	while(k)
	{
		ans = max(ans, tre[k]);
		k -= k&-k;
	}
	return ans;
}
int main(void)
{
	int n, i, t, p, now;
	scanf("%*d%d", &n);
	for(i=1;i<=n;i++)
	{
		scanf("%d%d%d", &t, &p, &s[i].val);
		s[i].x = 2*t+p;
		s[i].y = 2*t-p;
		Hash[i] = s[i].x;
	}
	sort(s+1, s+n+1);
	sort(Hash+1, Hash+n+1);
	cnt = unique(Hash+1, Hash+n+1)-(Hash+1);
	for(i=1;i<=n;i++)
	{
		p = lower_bound(Hash+1, Hash+cnt+1, s[i].x)-Hash;
		now = Query(p)+s[i].val;
		Update(p, now);
	}
	printf("%d\n", Query(cnt));
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值