[ARC108]Random IS
题目
题解
区间 \(DP\) 一眼,问题在于如何降低转移复杂度.
定义 \(f[l][r]\) 表示将 \(a[l]\) 与 \(a[r]\) 选择之后,区间的期望代价,这样定义我们需要在两边加俩哨兵.
十分容易列出转移:
\[f[i][j]=1+\sum_{i<k<j}^{a_i<a_k<a_j}\frac{f[i][k]+f[k][j]}{|S|}
\]
这样转移 \(\mathcal O(n)\),总复杂度 \(\mathcal O(n^3)\),对于这个数据范围会超时,考虑进行优化,首先将转移变形:
\[f[i][j]=1+\sum_{i<k<j}^{a_i<a_k<a_j}\frac{f[i][k]}{|S|}+\sum_{i<k<j}^{a_i<a_k<a_j}\frac{f[k][j]}{|S|}
\]
注意到两个求和的条件类似于二维偏序,我们考虑用枚举顺序解决一维,用数据结构解决另一维.
此处必须进行说明,如果我们用传统的区间 \(DP\) 打法,即枚举区间长度与左边界是无法进行优化的,因为我们的枚举顺序需要能够解决 \(i<k<j\) 这一维,故而我们采用另一种枚举方法 —— \(i\) 从大向小枚举,\(j\) 从小向大枚举,每次访问到某一个 \(j\) 就将其插入树状数组以解决第一个求和,同时,对于每一个 \(j\),我们将当前的 \(i\) 对应值插入权值树状数组,每一个 \(j\) 都需要开一个树状数组,这样就可以解决第二个求和.
代码
#include<bits/stdc++.h>
using namespace std;
namespace IO{
#define rep(i,l,r) for(int i=l,i##_end_=r;i<=i##_end_;++i)
#define fep(i,l,r) for(int i=l,i##_end_=r;i>=i##_end_;--i)
#define fi first
#define se second
#define Endl putchar('\n')
#define writc(x,c) fwrit(x),putchar(c)
typedef long long ll;
typedef pair<int,int> pii;
template<class T>inline T Max(const T x,const T y){return x<y?y:x;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x<0?-x:x;}
template<class T>inline void getMax(T& x,const T y){x=Max(x,y);}
template<class T>inline void getMin(T& x,const T y){x=Min(x,y);}
template<class T>T gcd(const T x,const T y){return y?gcd(y,x%y):x;}
template<class T>inline T readin(T x){
x=0;int f=0;char c;
while((c=getchar())<'0' || '9'<c)if(c=='-')f=1;
for(x=(c^48);'0'<=(c=getchar()) && c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T>void fwrit(const T x){
if(x<0)return putchar('-'),fwrit(-x);
if(x>9)fwrit(x/10);putchar(x%10^48);
}
}
using namespace IO;
const int maxn=2001;
const int mod=1e9+7;
int n,a[maxn+5];
inline int lowbit(const int i){return i&(-i);}
struct BIT{
int a[maxn+5];
inline void clear(){
rep(i,0,n)a[i]=0;
}
inline void modify(int i,const int x){
for(;i<=n;i+=lowbit(i))a[i]=(a[i]+x)%mod;
}
inline int query(int i){int ret=0;
for(;i;i-=lowbit(i))ret=(ret+a[i])%mod;
return ret;
}
inline int query(const int l,const int r){
return (query(r)-query(l-1)+mod)%mod;
}
};
BIT r[maxn+5];// 每个左端点建一个
BIT cnt;// 对个数维护一个数组
BIT l; // 对左端点开一个数组
int f[maxn+5][maxn+5];
int inv[maxn+5],fac[maxn+5];
inline int qkpow(int a,int n){
int ret=1;
for(;n;n>>=1,a=1ll*a*a%mod)if(n&1)
ret=1ll*ret*a%mod;
return ret;
}
inline void Init(){
n=readin(1);
rep(i,1,n)a[i]=readin(1);
++n;
a[n]=n;// 加一个哨兵
fac[0]=1;
rep(i,1,n)fac[i]=1ll*fac[i-1]*i%mod;
inv[n]=qkpow(fac[n],mod-2);
fep(i,n-1,1)inv[i]=1ll*inv[i+1]*(i+1)%mod;
inv[0]=1;
fep(i,n,1)inv[i]=1ll*inv[i]*fac[i-1]%mod;
// rep(i,0,n)writc(a[i],' ');Endl;
}
signed main(){
Init();
fep(i,n-2,0){
// printf("Now i == %d\n",i);
cnt.clear(),l.clear();
rep(j,i+2,n){
// printf("Now i == %d, j == %d\n",i,j);
r[j].modify(a[i+1],f[i+1][j]);
l.modify(a[j-1],f[i][j-1]);
cnt.modify(a[j-1],1);
// printf("After modify\n");
// 这个区间里符合 a[i]<a[k]<a[j] 的
// 此处不能取模
int down=cnt.query(a[j]-1)-cnt.query(a[i]);
if(down<=0)continue;// 如果没有满足的, 那么直接下一个
// f[i][k] 的求和
int s1=l.query(a[i]+1,a[j]-1);
// f[k][r] 的求和
int s2=r[j].query(a[i]+1,a[j]-1);
s1=(s1+s2)%mod;
f[i][j]=(1ll*s1*inv[down]%mod+1)%mod;
// printf("s1 == %d, down == %d\n",s1,down);
// printf("inv[%d] == %d\n",down,inv[down]);
// printf("f[%d, %d] == %d\n",i,j,f[i][j]);
}
}
printf("%d\n",f[0][n]);
return 0;
}