0
点赞
收藏
分享

微信扫一扫

小沙的remark(朴素思路、优化DP+树状数组OR线段树)

题目

原题链接


问题描述

给定 n ( 1 ≤ n ≤ 2 ∗ 1 0 6 ) n(1\leq n\leq2∗10^6) n(1n2106) s e e d seed seed,将基于 s e e d seed seed生成两个长度为 n n n的序列 a i a_i ai b i ( 0 ≤ b i < i ) b_i(0\leq b_i<i) bi(0bi<i)
我们的任务就是从序列 a i a_i ai中依照规则进行选择,问我们有多少种选择策略,结果对 1 e 9 + 7 1e9+7 1e9+7取模。
规则有两条,
第一:我们选择的 a i a_i ai不能小于上一次选择的元素。
第二:若要选择 a i a_i ai,需要保证在序列段 a ( i − b i ) a_{(i-b_i)} a(ibi) a i − 1 a_{i-1} ai1之间存在被选择过的元素,否则不能选择 a i a_i ai
在这里插入图片描述


分析

朴素思路

直接进行DP

    dp[1]=1;
	for(int i=2;i<=n;i++){
        dp[i]=dp[i-1]+1;
		for(int j=i-1;j>=i-b[i];j--){
			if(a[i]>=a[j]){
				(dp[i]+=dp[j]-dp[j-1])%mod;
			}
		}
	}

状态 d p [ i ] dp[i] dp[i]表示长度为 i i i的序列的方案数,边界条件为 d p [ 1 ] = 1 dp[1]=1 dp[1]=1,也就是只有一个元素时方案数为 1 1 1
对于 d p [ i ] dp[i] dp[i],首先 d p [ i ] = d p [ i − 1 ] + 1 dp[i]=dp[i-1]+1 dp[i]=dp[i1]+1,也就是在 d p [ i − 1 ] dp[i-1] dp[i1]的基础上,考虑一种只选择元素 a i a_i ai的情况;
之后,考虑在选择了元素 a i a_i ai的基础上,与前序元素的承接,如果在区间 [ a i − b [ i ] , a i − 1 ] [a_{i-b[i]},a_{i-1}] [aib[i],ai1]中,存在元素 a j a_j aj小于等于 a i a_i ai,那么 ( d p [ i ] + = d p [ j ] − d p [ j − 1 ] ) % m o d (dp[i]+=dp[j]-dp[j-1])\%mod (dp[i]+=dp[j]dp[j1])%mod d p [ i ] dp[i] dp[i]要加上 d p [ j ] − d p [ j − 1 ] dp[j]-dp[j-1] dp[j]dp[j1]
所谓的承接就是在这种情况下同时选择了 a i a_i ai a j a_j aj,但 d p [ j ] dp[j] dp[j]中包含了选择 a j a_j aj和没有选择 a j a_j aj的情况,所以要用 d p [ j ] − d p [ j − 1 ] dp[j]-dp[j-1] dp[j]dp[j1]

但很明显可以看出,这种方法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),会超时,所以我们不使用这种方法。


树状数组+优化DP

之前朴素DP不理想的原因是进行了双重循环,所以时间复杂度为 O ( n 2 ) O(n^2) O(n2),会超时,那么我们接下来的思路就是对这个循环进行优化。
在第二重循环中,我们的工作是依次访问区间 [ a ( i − b i ) , a i − 1 ] [a_{(i-b_i)},a_{i-1}] [a(ibi),ai1]中的元素,正是因为我们此时无法得知在这个区间中的大小情况,所以才需要依次访问,如果我们可以直接得知这一区间的情况就可以优化循环

树状数组小结——>求逆序对数
求逆序对数是一道经典例题,解法多样,用树状数组解法时的思路与这道题有类似的地方,可以借鉴一下。

我们定义状态 d p [ i ] dp[i] dp[i]为以 a i a_i ai作为最后一个元素的选择方案数。
再对序列 a i a_i ai依照大小进行从小到大的排序,然后依照大小依次处理,将其依次插入到 d p dp dp数组中,每次处理时就不必依次访问区间 [ a i − b [ i ] , a i − 1 ] [a_{i-b[i]},a_{i-1}] [aib[i],ai1]中的元素。

d p [ i ] dp[i] dp[i]为例, d p [ i ] = 1 + ∑ k = i − b i i − 1 d p [ k ] dp[i]=1+\sum_{k=i-b_i}^{i-1}dp[k] dp[i]=1+k=ibii1dp[k]
由于我们是采用从小到大的处理顺序,所以区间中出现 a k > a i a_k>a_i ak>ai时,对应的 d p [ k ] dp[k] dp[k]还是 0 0 0,所以此时实际相加的还是元素值小于等于 a i a_i ai的情况。

最后输出整个数组的和就是我们最后的解。

由于过程中需要频繁的求区间和,所以要借助树状数组来优化时间,当然了,用其他可以处理RMQ问题的方法同样可以优化时间。
ST表,处理静态RMQ,虽然可以处理RMQ,但由于更新操作过多,所以不适合;
线段树,处理一般RMQ,值得一试。

代码

#include<bits/stdc++.h>
namespace GenHelper
{
    int z1,z2,z3,z4,z5,u,res;
    int get()
    {
        z5=((z1<<6)^z1)>>13;
        z1=((int)(z1&4294967)<<18)^z5;
        z5=((z2<<2)^z2)>>27;
        z2=((z2&4294968)<<2)^z5;
        z5=((z3<<13)^z3)>>21;
        z3=((z3&4294967)<<7)^z5;
        z5=((z4<<3)^z4)>>12;
        z4=((z4&4294967)<<13)^z5;
        return (z1^z2^z3^z4);
    }
    int read(int m) {
        u=get();
        u>>=1;
        if(m==0)res=u;
        else res=(u/2345+1000054321)%m;
        return res;
    }
     void srand(int x)
    {
        z1=x;
        z2=(~x)^(0x23333333);
        z3=x^(0x12345798);
        z4=(~x)+51;
      	u = 0;
    }
}
using namespace GenHelper;
using namespace std;
typedef long long ll;
const int N=2e6+7,mod=1e9+7;
int a[N],b[N],n;
int dp[N];
pair<int,int>p[N];
void update(int i , int k) {
	while(i<=n)dp[i]=(dp[i]+k)%mod,i+=i&-i;
}
int getsum(int i){
	int sum=0;
    if(i<=0)return 0;
    while(i)sum=(sum+dp[i])%mod,i -= i&-i;
    return sum;
}
int main(){
    int seed;
    scanf("%d %d",&n,&seed);
	srand(seed);
	for(int i=1;i<=n;i++){
		a[i]=read(0),b[i]=read(i);
		p[i].first=a[i],p[i].second=i;
	}
	sort(p+1,p+1+n);
	for(int i=1;i<=n;++i){
		int k=p[i].second;
		int tmp=(getsum(k-1)-getsum(k-b[k]-1)+1+mod)%mod;
		update(k,tmp);
	}
	cout<<getsum(n);
    return 0;
}
举报

相关推荐

0 条评论