CDQ分治好题,至少对于我这个CDQ分治初学者
题目:
求区间不下降子序列个数
且
a
i
≤
k
,
k
≤
20
,
N
≤
5
×
1
0
4
,
Q
≤
2
×
1
0
5
a_i\leq k,k\leq 20,N\leq 5\times 10^4,Q\leq 2\times 10^5
ai≤k,k≤20,N≤5×104,Q≤2×105
题解:
很难想到,要用CDQ分治
要分了之后怎么进行维护复杂度尽量小,这是CDQ分治很难受的地方。
- 定义数组
在每个 s o l v e ( l , r ) solve(l,r) solve(l,r)中都有
f i , j 表 示 f_{i,j}表示 fi,j表示
( l ≤ j ≤ m i d ) j 为 左 端 点 , k ∈ [ j , m i d ] 中 所 有 a k = i 的 k 为 右 端 点 , 不 下 降 子 序 列 个 数 (l\leq j \leq mid)j为左端点 , k\in [j,mid]中所有a_{k}=i的k为右端点,不下降子序列个数 (l≤j≤mid)j为左端点,k∈[j,mid]中所有ak=i的k为右端点,不下降子序列个数
( m i d < j ≤ r ) j 为 右 端 点 , k ∈ ( m i d , j ] 中 所 有 a k = i 的 k 为 左 端 点 , 不 下 降 子 序 列 个 数 (mid<j \leq r)j为右端点 , k\in (mid,j]中所有a_{k}=i的k为左端点,不下降子序列个数 (mid<j≤r)j为右端点,k∈(mid,j]中所有ak=i的k为左端点,不下降子序列个数 - 维护数组
尽可能的找数组之间的联系
首先因为 m i d mid mid将 f i , j f_{i,j} fi,j分为了两个部分,意义不同(其实泛泛来说差不多),所以我们也分两部分讨论
这里我们先只分析右边的,左边的同理理解
f i , j = [ a j = = i ] + ∑ m i d ≤ k < j , a k ≤ a j f i , k f_{i,j}= \ [a_j==i]+\sum_{mid\leq k<j,a_k\leq a_j}f_{i,k} fi,j= [aj==i]+mid≤k<j,ak≤aj∑fi,k
然后我开始看到这式子,是懵逼的
[ a j = = i ] [a_j==i] [aj==i]指如果 a j = = i a_j==i aj==i的话, j − > j j->j j−>j就是一个合法区间, f i , j + 1 f_{i,j}+1 fi,j+1
∑ m i d ≤ k < j , a k ≤ a j f i , k \sum_{mid\leq k<j,a_k\leq a_j}f_{i,k} ∑mid≤k<j,ak≤ajfi,k指与前面的已合法区间拼接,因为还要满足拼接后不下降,所以 a k ≤ a j a_k\leq a_j ak≤aj
于是我们可以想到树状数组维护(略)
意右边循环顺序是 m i d + 1 − > r mid+1->r mid+1−>r,但左边循环顺序是 m i d − > l mid->l mid−>l,因为是逆着来的,所以注意树状数组也要逆一下
注意:
因为空集也算合法区间,所以要把 f 1 , m i d , f k , m i d + 1 f_{1,mid},f_{k,mid+1} f1,mid,fk,mid+1都要+1
- 询问求值
yy一下我们想到,我们需要将我们的 f i , j f_{i,j} fi,j前缀和一下(左边是后缀和)
我们先只考虑跨过 m i d mid mid的询问区间(ql,qr分别为询问的左右端点)
a n s = ∑ 1 ≤ i ≤ k F i , q l ∑ i ≤ j ≤ k F j , q r ans=\sum_{1\leq i\leq k}F_{i,ql}\sum_{i\leq j\leq k}F_{j,qr} ans=1≤i≤k∑Fi,qli≤j≤k∑Fj,qr
理解下,就是左右两端拼接,乘法原理
(我曾迷惑: q l − > s , s − > q r ql->s,s->qr ql−>s,s−>qr, m i d < s mid<s mid<s的情况没有考虑啊,但其实将 s s s移到 s ‾ < m i d \overline{s}<mid s<mid, q l − > s ‾ , s ‾ < q r ql->\overline{s},\overline{s}<qr ql−>s,s<qr这样是等效的) - 复杂度 O ( n k l o g n l o g k + q k ) O(nklognlogk+qk) O(nklognlogk+qk)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const int N=5e4+10,K=25,Q=2e5+10;
int n,k,q;
int a[N];
struct qus{int l,r;}qs[Q];
ll ans[Q];
ll f[K][N];
//---
ll t[K];
int lowbit(int x){return x&(-x);}
void clear(){memset(t,0,sizeof(t));}
void add(int i,int x){for(;i<=k;i+=lowbit(i))t[i]=(t[i]+x)%mod;}
ll qur(int i){ll res=0;for(;i;i-=lowbit(i))res=(res+t[i])%mod;return res;}
//----
ll ls[N];
void solve(int l,int r,vector<ll>v)
{
if(l==r)
{
for(int i=0;i<v.size();i++)ans[v[i]]=2;
return ;
}
int mid=(l+r)>>1;
for(int i=1;i<=k;i++)
{
clear();
for(int j=mid;j>=l;j--)
{
f[i][j]=qur(k+1-a[j]);
if(a[j]==i)f[i][j]=(f[i][j]+1)%mod;//自己->自己
add(k+1-a[j],f[i][j]);//逆转树状数组
}
for(int j=mid-1;j>=l;j--)f[i][j]=(f[i][j]+f[i][j+1])%mod;//前缀和
}
for(int j=l;j<=mid;j++)f[1][j]=(f[1][j]+1)%mod;//空集
for(int i=1;i<=k;i++)
{
clear();
for(int j=mid+1;j<=r;j++)
{
f[i][j]=qur(a[j]);
if(a[j]==i)f[i][j]=(f[i][j]+1)%mod;
add(a[j],f[i][j]);
}
for(int j=mid+2;j<=r;j++)f[i][j]=(f[i][j]+f[i][j-1])%mod;
}
for(int j=mid+1;j<=r;j++)f[k][j]=(f[k][j]+1)%mod;
vector<ll>vl,vr;
vl.resize(0);vr.resize(0);
for(int i=0;i<v.size();i++)
{
int l=qs[v[i]].l,r=qs[v[i]].r;
if(r<=mid) vl.push_back(v[i]);
else if(mid<l)vr.push_back(v[i]);
else
{
for(int j=k;j>=1;j--)ls[j]=(ls[j+1]+f[j][r])%mod;//前缀又后缀。。。
for(int j=1;j<=k;j++)ans[v[i]]=(ans[v[i]]+ls[j]*f[j][l]%mod)%mod;
}
}
if(vl.size())solve(l,mid,vl);
if(vr.size())solve(mid+1,r,vr);
}
vector<ll>v;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
scanf("%d",&q);
for(int i=1;i<=q;i++)scanf("%d%d",&qs[i].l,&qs[i].r);
for(int i=1;i<=q;i++)v.push_back(i);
solve(1,n,v);
for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
}