2018 HDU 多校第一场 1008:RMQ Similar Sequense

题意

定义运算

R M Q ( A , l , r ) = max i = l r A [ i ]

定义两个等长的数组A、B
R M Q S i m i l a r i { 1 , n } j { i , n } R M Q ( A , i , j ) = R M Q ( B , i , j )

给定A数组,限制B数组的元素在0-1之间随机取值,如果B similar with A,B的权值定义为所有元素的和,否则为0.
问B的期望权值。

题解:

铁头娃上来就想用积分分治乱搞,结果被队友证明积分并不能救这个题。。

dls直播讲了这个题代码极其简单,那么我来复现顺便证明一下。。
下边的Bi表示任意一种随机生成的B数列,[Bi]=1代表Bi和A是Similar 否则[Bi]=0,类似艾佛森规约的写法,记|Bi|为Bi的个数

E ( W e i g h t B ) = 1 | B i | B i [ B i ] S u m B i = B i S u m B i | B i | [ B i = 1 ] S u m B i B i S u m B i = E ( B i ) [ B i = 1 ] S u m B i B i S u m B i = n 2 | [ B i ] = 1 | E ( S u m [ B i ] = 1 ) | B i | E ( S u m B i )

由于单点的概率=0,比如说生成[0.1,0.2,0.3]这种数列的概率为0,那么可以认为每一个元素不相等。
由于每一个Bi取值的独立性,因此Similar的Sum_Bi的期望也是n/2,因为生成任意一个B数列相当于排列问题,而生成Similar的B数列就是组合问题,意思是先生成n个数字,然后按照规定的顺序(即A数组决定的全序关系)排列即可。

E ( W e i g h t B ) = n 2 | [ B i ] = 1 | E ( S u m [ B i ] = 1 ) | B i | E ( S u m B i ) = n 2 | [ B i ] = 1 | n 2 | B i | n 2 = n 2 | [ B i ] = 1 | | B i | = n 2 P ( [ B i ] = 1 )

推了这么半年。。最后要求Bi和A是Similar的概率。由于我们认为Bi的每个元素不相等。因此我们从小到大把Bi的元素重新标号成1..n,明显可以看出每种全排列是等可能的基本事件。。因此我们把一个连续的问题。。变成离散的了。。。然后就极其容易思考了。。。。
设Bj为一个n的全排列,A按照全序关系从小到大标号为1..n设为Ax排列

E ( W e i g h t B ) = n 2 P ( [ B i ] = 1 ) = n 2 | B j S i m i l a r w i t h A x | n ! = n 2 n ! | B j S i m i l a r w i t h A x |

好的。。。问题现在变成了。。多少个n的全排列和Ax是Similar的。。。

因此,本题的解法刚刚开始。。。


首先观察最大的元素在A和B中的位置,显然要求他们位置相同。因为包含这个最大元素的区间的RMQ 相等。
然后考虑不包含这个最大元素的区间,显然这样的区间完全位于最大元素的左边/右边。进而我们看到了一个分治的模型:每次确定区间最大值。然后分治左右。
也就是dls说的A、B两个数组的笛卡尔树形态完全一样。
对于笛卡尔树上的某个点,显然我们要把这一段区间的最大值给这个点,然后我们把剩下的点分成两堆,分给左右子树。于是我们有一个递归的方案数计算公式:
F(x)表示给定|x|个元素,以x为根的整个子树的方案个数,左儿子记为L(x),右儿子记为R(x),树的大小记为|x|

F ( x ) = C | x | 1 | L ( x ) | F ( L ( x ) ) F ( R ( x ) )

好像就。。做完了。。
本题还有一个直觉上秒杀的方法。。证明不是特别的直觉,直接计算P([Bi]=1),设笛卡尔树的根为rt
P ( [ B i ] = 1 ) = P ( B [ r t ] = n ) P ( B [ L ( r t ) ] = L ( r t ) ) P ( B [ R ( r t ) = R ( r t ) ] ) . . . = i = 1 n P ( B [ i ] = i ) = i = 1 n 1 | i | = i = 1 n 1 S i z e ( i )

考虑使用数学归纳法证明:

假设第一个公式中 F ( x ) = | x | ! i x 1 | i |

如果一个点的左右子树Size都小于等于1,结论显然成立。
假设一个点x左子树Size=n,右子树Size=m,
即|x|=n+m+1 ; |L(x)|=n ; |R(x)| = m 那么根据第一个公式
F ( x ) = C | x | 1 | L ( x ) | F ( L ( x ) ) F ( R ( x ) ) = C n + m n F ( L ( x ) ) F ( R ( x ) ) = ( m + n ) ! n ! m ! n ! i L ( x ) | i | m ! j R ( x ) | j | = ( m + n ) ! i { R ( x ) , L ( x ) } | i | = ( m + n + 1 ) ! | x | i { R ( x ) , L ( x ) } | i | = | x | ! i x | i |
归纳一波,假设成立
进而 F ( r t ) = n ! i | i |
于是 P ( [ B i ] = 1 ) = F ( r t ) n ! = 1 i | i | = 1 i S i z e i

下面两份代码分别基于第一个和第二个公式。ps:数据太大,需要开栈。

//
// Created by calabash_boy on 18-7-24.
//他的名字是笛卡尔树。
//

#include<bits/stdc++.h>
using namespace std;
#define OPENSTACK

const int maxn = 1e6+100;
const int mod = 1e9+7;
typedef long long LL;
int stk[maxn],top;
int l[maxn],r[maxn],rt;
int n;
pair<int,int> a[maxn];
LL inv[maxn];
LL fac[maxn];
LL inv_fac[maxn];
int sz[maxn];
bool vis[maxn];
/* l 左儿子 r 右儿子 rt根*/
void build(){
    top=0;
    for (int i=1;i<=n;i++) l[i]=r[i]=vis[i] =0;
    for (int i=1;i<=n;i++){
        int k = top;
        while (k&&a[i]<a[stk[k-1]])k--;
        if (k) r[stk[k-1]] = i;
        if (k<top) l[i] = stk[k];
        stk[k++] =i;
        top = k;
    }
    for (int i=1;i<=n;i++) vis[l[i]] = vis[r[i]] =1;
    for (int i=1;i<=n;i++){
        if (!vis[i]){
            rt = i;
            break;
        }
    }
}
LL power(LL x,LL y){
    LL res =1;
    while (y){
        if (y&1)res = res*x%mod;
        y>>=1;
        x = x*x%mod;
    }
    return res;
}
inline LL C(int n,int m){
    return fac[n]*inv_fac[m]%mod*inv_fac[n-m]%mod;
}
int dfs(int u){
    sz[u]=1;
    int ans =1;
    if (l[u])ans=1LL*ans*dfs(l[u])%mod;
    if (r[u])ans = 1LL*ans*dfs(r[u])%mod;
    sz[u]+=sz[l[u]]+sz[r[u]];
    return 1LL*ans*C(sz[u]-1,sz[l[u]])%mod;
}
void Main(){
    inv[1]=fac[1]=fac[0]=1;
    for (int i=2;i<maxn;i++)fac[i] = fac[i-1]*i%mod,inv[i] = inv[mod%i]*(mod-mod/i)%mod;
    inv_fac[maxn-1] = power(fac[maxn-1],mod-2);
    for (int i=maxn-2;i>=0;i--){
        inv_fac[i] = inv_fac[i+1]*(i+1)%mod;
    }
    int T;
    scanf("%d",&T);
    while (T--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            int x;
            scanf("%d", &x);
            a[i] = {-x, i};
        }
        build();
        printf("%d\n", inv[2] * n % mod * power(fac[n], mod - 2) % mod * dfs(rt) % mod);
    }
}
int main(){
#ifdef OPENSTACK
    int size = 70 << 20; // 256MB
    char *p = (char*)malloc(size) + size;
#if (defined _WIN64) or (defined __unix)
    __asm__("movq %0, %%rsp\n" :: "r"(p));
#else
    __asm__("movl %0, %%esp\n" :: "r"(p));
#endif
#endif

    Main();
#ifdef OPENSTACK
    exit(0);
#else
    return 0;
#endif

}
// H

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
// head

const int N=1010000;
int stk[N],top,l[N],r[N],vis[N],n,x,_;
PII a[N];
ll inv[N],ret;


int dfs(int u) {
    int s=1;
    if (l[u]) s+=dfs(l[u]);
    if (r[u]) s+=dfs(r[u]);
    ret=ret*inv[s]%mod;
    return s;
}
void build() {
    int top=0;
    rep(i,1,n+1) l[i]=0,r[i]=0,vis[i]=0;
    rep(i,1,n+1) {
        int k=top;
        while (k>0&&a[stk[k-1]]>a[i]) --k;
        if (k) r[stk[k-1]]=i;
        if (k<top) l[i]=stk[k];
        stk[k++]=i;
        top=k;
    }
    rep(i,1,n+1) vis[l[i]]=vis[r[i]]=1;
    int rt=0;
    rep(i,1,n+1) if (vis[i]==0) rt=i;
    dfs(rt);
}

int main() {
    inv[1]=1;
    rep(i,2,1000001) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
    for (scanf("%d",&_);_;_--) {
        scanf("%d",&n);
        rep(i,1,n+1) {
            scanf("%d",&x);
            a[i]=mp(-x,i);
        }
        ret=inv[2]*n%mod;
        build();
        printf("%lld\n",ret);
    }
}

猜你喜欢

转载自blog.csdn.net/calabash_boy/article/details/81181956