【训练题47:概率dp | 动态规划 | 优化】Increasing Subsequence | 2021牛客暑期多校训练营1 I题

题意

  • Increasing Subsequence | 2021牛客暑期多校训练营1
    长度为 n n n 的数组,第 i i i 个数为 a i a_i ai , 且这个数组是一个 [ 1 , n ] [1,n] [1,n] 的全排列
    A l i c e Alice Alice 先等概率随机选择一个位置 x x x , 然后 B o b Bob Bob 也等概率随机选择一个位置 y y y ,但要求 a y > a x a_y>a_x ay>ax
    然后 A l i c e Alice Alice 先,轮流两人操作:
  • 假设 ta 上一步选择的下标为 x x x ,对方上一步选择的下标为 y y y
    那么 ta 必须选择一个位置 k k k , 满足 k > x k > x k>x a k > a y a_k>a_y ak>ay
    多有多个满足的位置,那么 ta 会等概率随机选择一个
    若没有满足的位置,那么结束
    问你从开始到结束,轮次的期望值
  • 1 ≤ n ≤ 5000 1\le n\le 5000 1n5000

思路

  • 看到期望,还有数据范围,肯定会去想一个 期望的 d p dp dp
    d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 A l i c e Alice Alice 拿了下标 i i i B o b Bob Bob 拿了下标 j j j ,到结束的轮次的期望
    我们根据 a i 、 a j a_i、a_j aiaj 的大小关系可以自己判断下一步是谁拿数字
    假设 a i > a j a_i>a_j ai>aj ,那么就是 B o b Bob Bob 拿,我们自己可以写出转移:
    d p [ i ] [ j ] = 1 c ∑ k > j a k > a i d p [ i ] [ k ] + 1 dp[i][j]=\frac{1}{c}\sum_{\underset{a_k>a_i}{k>j}}dp[i][k] + 1 dp[i][j]=c1ak>aik>jdp[i][k]+1
    假设 a i < a j a_i<a_j ai<aj,那么就是 A l i c e Alice Alice 拿:
    d p [ i ] [ j ] = 1 c ∑ k > i a k > a j d p [ k ] [ j ] + 1 dp[i][j]=\frac{1}{c}\sum_{\underset{a_k>a_j}{k>i}}dp[k][j] + 1 dp[i][j]=c1ak>ajk>idp[k][j]+1
    这里 c c c 就是有多少个 k k k 满足以上要求
    但是这样直接去做当然是 O ( N 3 ) O(N^3) O(N3) 的,直接 T L E \color{red}{TLE} TLE
    当时我用开 4 n 4n 4n 个树状数组去做,但是 O ( N 2 log ⁡ N × c ) O(N^2\log N\times c) O(N2logN×c) T L E \color{red}{TLE} TLE
    实际上,我们可以优化到 O ( N 2 ) \color{cyan}{O(N^2)} O(N2)
  • 两个状态维度都是必须要的,也就是说我们只能去 O ( 1 ) O(1) O(1) 获得后面的那个求和
    因为我们写代码肯定是这样:
	for(int i = n;i >= 1;--i)
	for(int j = n;j >= 1;--j){
		dp[i][j] = Something
	}
  • 对于 B o b Bob Bob 拿的情况,我们需要快速求出 ∑ k > j a k > a i d p [ i ] [ k ] \sum_{\underset{a_k>a_i}{k>j}}dp[i][k] ak>aik>jdp[i][k]
    因为我们就是 j j j n n n 倒序枚举到现在的,所以我们只要对当前的 i i i ,记录有多少个数满足 a k > a i a_k>a_i ak>ai,并且把他们的 d p [ i ] [ k ] dp[i][k] dp[i][k] 的和记录下来,就可以 O ( 1 ) O(1) O(1) 拿到了
    大概就是这样,很好理解:
	for(int i = n;i >= 1;--i){
        ll c = 0;
        ll sum1 = 0;
        for(int j = n;j >= 1;--j){
            if(i == j)continue;
            if(aa[i] > aa[j]){
                dp[i][j] = (1 + iv[c] * sum1) % MOD;		// iv[c] = 1 / c
            }else{
                c++;
                sum1 = (sum1 + dp[i][j]) % MOD;
            }
        }
    }
  • 对于 A l i c e Alice Alice 拿的情况,我们需要快速求出 ∑ k > i a k > a j d p [ k ] [ j ] \sum_{\underset{a_k>a_j}{k>i}}dp[k][j] ak>ajk>idp[k][j]
    因为我们的 i i i 也是从 n n n 倒序枚举到现在的,就默认满足了 k > i k>i k>i
    对于所有 a k > a j a_k>a_j ak>aj 的情况,我们需要记录有多少次满足,以及 d p [ k ] [ j ] dp[k][j] dp[k][j] 的和
    这里 j j j 有很多种值,那么我们干脆开一个数字 c n t [ j ] 、 s u m [ j ] cnt[j]、sum[j] cnt[j]sum[j] 存对应满足的内容
    for(int i = n;i >= 1;--i){
        ll c = 0;
        ll sum1 = 0;
        for(int j = n;j >= 1;--j){
            if(i == j)continue;
            if(aa[i] > aa[j]){
                dp[i][j] = (1 + iv[c] * sum1) % MOD;
                cnt[j]++;
                sum[j] = (sum[j] + dp[i][j]) % MOD;
            }else{
                dp[i][j] = (1 + iv[cnt[j]] * sum[j]) % MOD;
                c++;
                sum1 = (sum1 + dp[i][j]) % MOD;
            }
        }
    }
  • 稍微再修改一下,因为一开始开局不是两个人一下子选好的,是先 A l i c e Alice Alice 选再 B o b Bob Bob
    那我们 B o b Bob Bob 的下标选到 0 0 0 则表示 B o b Bob Bob 还没开始选
    for(int i = n;i >= 1;--i){
        ll c = 0;
        ll sum1 = 0;
        for(int j = n;j >= 0;--j){
            if(i == j)continue;
            if(aa[i] > aa[j]){
                dp[i][j] = (1 + iv[c] * sum1) % MOD;
                cnt[j]++;
                sum[j] = (sum[j] + dp[i][j]) % MOD;
            }else{
                dp[i][j] = (1 + iv[cnt[j]] * sum[j]) % MOD;
                c++;
                sum1 = (sum1 + dp[i][j]) % MOD;
            }
        }
    }
  • 最后答案是什么?就是所有开局情况的期望 除以 开局的情况数
	for(int i = 1;i <= n;++i){
        ans = (ans + dp[i][0]) % MOD;
    }
    ans = ans * iv[n] % MOD;

代码

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 5e3+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;


ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

int aa[MAX];
ll dp[MAX][MAX];
ll iv[MAX];
ll sum[MAX],cnt[MAX];
int main()
{
    IOS;
    int n;cin >> n;
    for(int i = 1;i <= n;++i)cin >> aa[i],iv[i] = inv(i);
    iv[0] = 1;
    for(int i = n;i >= 1;--i){
        ll c = 0;
        ll sum1 = 0;
        for(int j = n;j >= 0;--j){
            if(i == j)continue;
            if(aa[i] > aa[j]){
                dp[i][j] = (1 + iv[c] * sum1) % MOD;
                cnt[j]++;
                sum[j] = (sum[j] + dp[i][j]) % MOD;
            }else{
                dp[i][j] = (1 + iv[cnt[j]] * sum[j]) % MOD;
                c++;
                sum1 = (sum1 + dp[i][j]) % MOD;
            }
        }
    }
    ll ans = 0;
    for(int i = 1;i <= n;++i){
        ans = (ans + dp[i][0]) % MOD;
    }
    ans = ans * iv[n] % MOD;
    cout << ans;
    return 0;
}
/**

*/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值