题意:
就是给你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;
}
题意:
就是多重背包求最大值。
思考:
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;
}
总结:
多多思考呀,把题目都集合起来去思考。