2019 计蒜之道 复赛 A. 外教 Michale 变身大熊猫

题意:

    求每个数在最长上升子序列中出现的概率。

题解:

   首先求出最长上升子序列有那些数,通过二分+dp可以求出最长上升子序列中每个数,是出现在哪个位置上,然后出现在同一位置的数的值一定的从大到小的。所以对于位置i 假设你当前选的是x[i][j]的后一个位置i+1可以选择的数一定是靠前出现的,因为后面出现的数可能比x[i][j]要小。根据此性质从后往前dp算可以填x[i][j]上去的概率,再从前往后dp可以填x[i][j]上去的概率,相乘后就是答案。复杂度 O(nlogn)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MX = 5e5+7;
const int mod = 998244353;
const int Sqr = sqrt(MX);
LL qpow(LL a, LL n){
    LL ret = 1;
    while(n > 0){
        if(n&1) ret = ret*a%mod;
        a = a*a%mod;
        n >>= 1;
    }
    return ret;
}

int a[MX],d[MX],tail;
vector<int> v[MX];
LL dp[MX], last[MX], pre[MX], d2[MX];
int ans[MX];

inline void upd(LL &x){
    x = (x%mod+mod)%mod;
}
int main()
{
#ifdef LOCAL
    freopen("in.txt","r",stdin);
#endif // LOCAL
    int n;
    scanf("%d",&n);
    for(int i = 1; i <= n ;i++){
        scanf("%d",&a[i]);
    }
    for(int i = 1; i <= n ;i++){
        int l = 1, r = tail;
        while(l <= r){
            int mid = (l+r)>>1;
            if(d[mid] < a[i]) l = mid+1;
            else r = mid-1;
        }
        d[l] = a[i];
        if(l > tail) ++tail;
        v[l].push_back(i);
    }

    for(int i = tail; i >= 1; i--){
        if(i == tail) {
            for(int j = v[i].size()-1; j >= 0; --j){
                int id = v[i][j];
                dp[id] = 1;
                last[id] = dp[id];
                if(j < v[i].size()-1) last[id] += last[v[i][j+1]];
//                cout<<i<<" "<<id<<endl;
            }
        }
        else{
            int k = v[i+1].size(), l = v[i+1].size();
            for(int j = v[i].size()-1; j >= 0; --j){
                int jid = v[i][j];
                for(--k; k >= 0; --k){
                    int kid = v[i+1][k];
                    if(kid < jid) break;
                }
                ++k;
                for(--l; l >= 0; --l){
                    int lid = v[i+1][l];
                    if(a[lid] > a[jid]) break;
                }
                ++l;
                if(k < v[i+1].size() && k < l){
                    dp[jid] = last[v[i+1][k]];
                    if(l < v[i+1].size()) dp[jid] -= last[v[i+1][l]];
                }
                else dp[jid] = 0;

//                cout<<i<<" "<<jid<<endl;
                last[jid] = dp[jid];
                if(j < v[i].size()-1) last[jid] += last[v[i][j+1]];
                upd(dp[jid]);
                upd(last[jid]);
//                cout<<i<<" "<<j<<" "<<last[jid]<<endl;
            }
        }
    }
    LL tot = 0;
    if(n) tot = last[v[1][0]];
    LL totinv = qpow(tot,mod-2);
    for(int i = 1; i <= tail; i++){
        if(i == 1){
            for(int j = 0; j < v[i].size(); ++j){
                int id = v[i][j];
                d2[id] = 1;
                pre[id] = d2[id];
                if(j > 0) pre[id] += pre[v[i][j-1]];
//                cout<<i<<" "<<j<<" "<<v[i][j]<<endl;
            }
        }
        else{
            int k = -1, l = -1;
            for(int j = 0; j < v[i].size(); ++j){
                int jid = v[i][j];
                for(++k; k < v[i-1].size(); ++k){
                    int kid = v[i-1][k];
                    if(kid > jid) break;
                }
                --k;
                for(++l; l < v[i-1].size(); ++l){
                    int lid = v[i-1][l];
                    if(a[lid] < a[jid]) break;
                }
                --l;
//                cout<<i<<" "<<v[i][j]<<endl;
                if(k >= 0 && k > l){
                    d2[jid] = pre[v[i-1][k]];
                    if(l < v[i-1].size()) d2[jid] -= pre[v[i-1][l]];
                }
                else d2[jid] = 0;
                pre[jid] = d2[jid];
                if(j > 0) pre[jid] += pre[v[i][j-1]];
                upd(d2[jid]);
                upd(pre[jid]);
//                cout<<i<<" "<<j<<" "<<" "<<k<<" "<<l<<" "<<pre[jid]<<endl;
            }
        }
        for(int j = 0; j < v[i].size(); ++j){
            int jid = v[i][j];
//            cout<<i<<" "<<jid<<" "<<dp[jid]<<" "<<d2[jid]<<" "<<tot<<endl;
            ans[jid] = 1LL*dp[jid]*d2[jid]%mod*totinv%mod;
        }
    }
    for(int i = 1; i <= n; i++){
        printf("%d%c",ans[i], i==n? '\n' : ' ');
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值