2022江苏省赛-Jump and Treasure-(单调队列优化dp+调和级数)

Jump and Treasure

题意:
就是给你n个点,然后每个点有正的金币和负的金币,每次你跳跃的距离不能超过p,然后问你跳超过n的时候,最多能有多少金币。如果跳不出n,那么输出-1。然后给你q次查询,每次给你个x,然后你只能跳到是x的倍数的点上,然后请你输出答案。

思考:
一看n很大,查询也很大,不用想肯定预处理。然后每次查询i,那么一共n个i直接每个枚举一遍,nlogn复杂度。然后对于子问题,如何处理呢,这种求最大值的。定义dp[i]一般有两种定义状态:1.为走到i点的最大值为多少,仅仅是走到这个点,并不是这个点是前面中的最大的(这种dp[i]的定义呢,一般都是走到i点,是必选i点。一般这种题都是i转移的时候,不能从前面任意一点转移,而是从一些点转移有了限制,所以就不能代表前面所有的dp。这种题目就像最长上升子序列)。2.走到i点的最大值是多少,i点的最大值,就是前面所有状态的最大值而这种题目,一般都是可以从前面的任意一点来转移,随便转移,可以代表前面所有的状态。这种题目就像背包
由于这个题目说的是跳过n点,所以必须走到某一点,而不是停留在前面,所以就是第一种,当为第一种的时候,dp[i]从前面转移的时候,肯定就是所有可能的点都要枚举一下,这里有个跳跃距离的限制,所以更新的时候可以用个单调队列优化。

插曲:当时现场赛,读完题目肯定是预处理,对每个数字i都算一遍复杂度nlogn。然后让你求金币最大最小,直接dp,然后转移就是该怎么转移。当时我想到用优先队列,求区间最大值。但是优先队列复杂度加了个logn,所以nlognlogn过不了。然后于说可以用单调队列。这个我一直没学,所以当时WA了好几发。还是把所有的基础算法学学吧。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
		
using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 1e6+10,M = 2010;

int T,n,m,k;
int va[N];
int dp[N]; //走到这个i点的最小花费,选择i点。
int anw[N];

int q[N],hh,tt;

bool check1(int a,int b)
{
	if(a+m<b) return true;
	return false;
}

bool check2(int a,int b)
{
	if(b>=a) return true;
	return false;
}

int solve(int x)
{
	if(x>m) return -1;
	hh = 0,tt = -1;
	q[++tt] = 0;
	for(int i=x;i<=n;i+=x)
	{
		while(hh<=tt&&check1(q[hh],i)) ++hh;
		dp[i] = dp[q[hh]]+va[i]; //这里先更新,你才能把dp[i]放进去比较
		while(hh<=tt&&check2(dp[q[tt]],dp[i])) --tt;
		q[++tt] = i;
	}
	while(hh<=tt&&check1(q[hh],n+1)) ++hh;
	return dp[q[hh]];
}

signed main()
{
	IOS;
	cin>>n>>k>>m;
	for(int i=1;i<=n;i++) cin>>va[i];
	for(int i=1;i<=n;i++) anw[i] = solve(i);
	while(k--)
	{
		int x;
		cin>>x;
		if(anw[x]==-1) cout<<"Noob\n";
		else cout<<anw[x]<<"\n";
	}
	return 0;
}

多重背包问题III

题意:
就是多重背包求最大值。

思考:
1.最简单的做法就是三重循环dp[i][j]为用到第i个,不超过j,最大价值。不能省略第一维,因为第二层枚举用多少个的时候,第一次是用的上一层的dp,但是第二次就可能用了这一层的dp,所以就不对了。
2.进一步优化可以把物品分类,因为2的次幂可以组成任意数,所以把物品分类后,进行01背包即可.
3.然后就是单调队列做法,其实就是把整个dp呢,看成好几个不同的dp,根据余数r来分的。对于每一种,转移的时候肯定也是从上一层来转移,所以,把上一层看成一格数组。然后每次取出最大值,来更新当前的dp。但是不同的j又加的数不同,所以又弄了一个偏移量,根据当前的j是多少来的。最重要的就是把他看成很多个dp。对于为啥有些题目用经典dp做的时候,需要用单调队列优化,因为,i从前面转移的时候,不能从1到i里面找最优值。因为题目会给你限制,让你从一段区间里面找,就是离i有一定的范围才行。所以dp[i]的状态,有时候是代表的从1到i最优的肯定在最后面。但是有时候最优的可能是在中间。这个想好就行了。如果在可能在中间那么要单调队列,如果就是在最近的,直接从最近的指针来转移就好了。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define db double
#define int long long
#define PII pair<int,int >
#define mem(a,b) memset(a,b,sizeof(a))
#define IOS std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);

using namespace std;
const int mod = 1e9+7,inf = 1e18;
const int N = 2e5+10,M = 2010;

int T,n,m,k;
int va[N];
int dp[N];
int tmp[N];

int q[N],hh,tt;

signed main()
{
    IOS;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        memcpy(tmp,dp,sizeof(dp)); //由于这个tmp中的值是不边的,但是不同的j转移的时候加的值不一样,所以这里弄了一个偏移量,加的值是根据转移多大来的。
        for(int r=0;r<a;r++)
        {
            hh = 0,tt = -1;
            for(int j=r;j<=m;j+=a)
            {
                while(hh<=tt&&q[hh]<j-c*a) ++hh; //如果开头的已经不能更新后面的了,就是滑动窗口要滑动了
                while(hh<=tt&&tmp[q[tt]]-(q[tt]-r)/a*b<=tmp[j]-(j-r)/a*b) --tt; //如果这个点能往前走就往前走。因为开头是最牛逼的。
                q[++tt] = j;
                dp[j] = tmp[q[hh]]+(j-q[hh])/a*b;
            }
        }
    }
    cout<<dp[m];
    return 0;
}

总结:
多多思考呀,把题目都集合起来去思考。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值