[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;
}
posted @ 2020-11-29 09:18  Arextre  阅读(98)  评论(0编辑  收藏  举报