题目
原题链接
问题描述
给定
n
(
1
≤
n
≤
2
∗
1
0
6
)
n(1\leq n\leq2∗10^6)
n(1≤n≤2∗106)和
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(0≤bi<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(i−bi)和
a
i
−
1
a_{i-1}
ai−1之间存在被选择过的元素,否则不能选择
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[i−1]+1,也就是在
d
p
[
i
−
1
]
dp[i-1]
dp[i−1]的基础上,考虑一种只选择元素
a
i
a_i
ai的情况;
之后,考虑在选择了元素
a
i
a_i
ai的基础上,与前序元素的承接,如果在区间
[
a
i
−
b
[
i
]
,
a
i
−
1
]
[a_{i-b[i]},a_{i-1}]
[ai−b[i],ai−1]中,存在元素
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[j−1])%mod,
d
p
[
i
]
dp[i]
dp[i]要加上
d
p
[
j
]
−
d
p
[
j
−
1
]
dp[j]-dp[j-1]
dp[j]−dp[j−1]。
所谓的承接就是在这种情况下同时选择了
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[j−1]。
但很明显可以看出,这种方法的时间复杂度为 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(i−bi),ai−1]中的元素,正是因为我们此时无法得知在这个区间中的大小情况,所以才需要依次访问,如果我们可以直接得知这一区间的情况就可以优化循环。
树状数组小结——>求逆序对数
求逆序对数是一道经典例题,解法多样,用树状数组解法时的思路与这道题有类似的地方,可以借鉴一下。
我们定义状态
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}]
[ai−b[i],ai−1]中的元素。
以
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=i−bii−1dp[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;
}