Hetao P1184 宝可梦训练家 [ 绿 ][ 背包dp ][ 线性dp ]

原题


题解

一道超级牛逼的背包变形,想通之后真的很简单,难点在于想到使用 dp 并且用 dp 的值判断是否合法。

首先观察本题的数据范围:1n,q105 ,可知本题的询问要在 O(1)~O(logn) 的时间里处理出来。

因此想到了二分或者预处理做法。

同时再来观察 k 的范围,1k50 ,这是个非常重要的切入点,同时把 a,l,r 的范围限制在 105+50 也是一个非常重要的条件。假设题目不需要利用这个条件,又为什么要将 k 限制在 50 ,而不开到 109 呢?这就告诉我们题目的重点不在 n 上,重点是要计算每个宝可梦变成的值。

这样就可以直接排除二分区间应对询问的做法,因此直接考虑预处理。


分析

先考虑最简单的预处理方式:定义 f[i][j] 表示能否变成数字[i,j] 的区间。

这种预处理方式不仅会 MLE ,而且会 TLE 。

但是我们也可以发现一个优化的点,那就是对于 f[i][j]=Yes 时,f[i+1][j]=Yes

同理,也可以说成是 f[i][j1]=Yes ,只要换一种方式存储和写代码即可,这里采用上面的一种优化方案。

因此,我们用 f[j] 存储以数字 j 为结尾,最长能有的区间的长度 len那么此时的 i=jlen+1。所以当 f[j]ji+1 时,这个区间就可以得到(ji+1 是这个区间的长度),实际上,存储 i 来逆推 len 也不是不可以。

那么此时我们就只剩处理出 f[j] ,即以每个数字 j 结尾的 len 就行了。


建立模型

可以发现,f[j] 可以看成一个容量为 j 的背包,那么它的价值便是 len ,我们要使 len 最大化。并且由于每个宝可梦只能使用一次,所以这是个 01背包

所以设 j=a[i]+v ,则 f[j]=f[j1]+1,其中 0vk,即宝可梦的可以变换成的数。

这个方程的意义是:以 a[i]+v 为结尾时,最长的区间是从 以 a[i]+v1 为结尾时最长区间的长度加了一个 1 得来的。

注意 01背包 的特殊转移方式,要让 vk0 转移,才能让使用了某个宝可梦的子集不包含也使用了这个宝可梦的子集。

唯一不同的是,背包加入物品是无序的,因为只要求 max 即可;但是这题加入是有序的,如果比它小的数没有更新完,而直接更新这个数,就会导致没有将这个状态分割成不遗漏也不重叠的子集,这是不行的。

因此,要先排个序再来给 f 更新。

时间 O(nk)

代码

#include <bits/stdc++.h>
using namespace std;
int f[100105],n,q,k,l,r,a[100005];
int main()
{
	freopen("pokemon.in","r",stdin);
	freopen("pokemon.out","w",stdout);
	cin>>n>>k>>q;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)
	{
		for(int j=k;j>=0;j--)
		{
			f[a[i]+j]=f[a[i]+j-1]+1;
		}
	}
	for(int i=1;i<=q;i++)
	{
		cin>>l>>r;
		if(f[r]>=r-l+1)cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
	return 0;
}
posted @   KS_Fszha  阅读(52)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示