[Codeforces 1295F]Good Contest(DP+组合数学)

[Codeforces 1295F]Good Contest(DP+组合数学)

题面

有一个长度为\(n\)的整数序列,第\(i\)个数的值在\([l_i,r_i]\)中随机产生。问这个序列是一个不上升序列的概率(模\(998244353\)意义下)。

\(n \leq 50,l_i,r_i \leq 998244351\)

分析

[APIO2016]划艇几乎一模一样。可惜比赛的时候时间不够。

首先把问题转化成求最长不上升序列的数量。

我们把这些区间离散化,分割成两两之间不互相覆盖的若干个区间,把这些分割后的区间从被拆分成编号在\([l_i,r_i)\)的新区间。如原来的区间为\([1,4],[2,3],[4,5]\).那么拆分出来的新区间有\(1:[1,1],2:[2,3],3:[4,4],4:[5,5]\).而新的\(l_i,r_i\)对应为\([1,3),[2,3),[3,5)\).下面的代码实现了该过程.

void discrete(){
    dn=0;
    for(int i=1;i<=n;i++){
        tmp[++dn]=a[i].l;
        tmp[++dn]=a[i].r+1;//转成开区间
    }
    sort(tmp+1,tmp+1+dn);
    dn=unique(tmp+1,tmp+1+dn)-tmp-1;
    for(int i=1;i<=n;i++){
        a[i].l=lower_bound(tmp+1,tmp+1+dn,a[i].l)-tmp;
        a[i].r=lower_bound(tmp+1,tmp+1+dn,a[i].r+1)-tmp;
    }
}

设\(dp[i][j]\)表示前\(i\)个数,第\(i\)个数在第\(j\)个新区间或之后的新区间内的方案数。那么从前面转移过来时的区间编号一定\(\geq j\). 于是可以写出转移方程:

\[dp[i][j]=\sum_{1 \leq k \leq i,j \in[l_k,r_k] } dp[k-1][j+1] \times C_{i-k+len(j)}^{i-k+1}\]

我们枚举最前面的与\(i\)在同一个新区间\(j\)的位置\(k\), 那么 比\(k\)小的位置所在区间编号一定\(>j\),所以乘上\(dp[k-1][j+1]\). 而\(len(j)\)表示的是第\(j\)个新区间的实际长度,组合数表示的是从区间\(j\)里选出\(i-k+1\)个递增的数.

注意到上面的转移方程只求出了第\(i\)个数恰好在区间\(j\)里的方案数。所以枚举完\(j\)后还要求一遍后缀和。

最终答案为\(\frac{dp[n][1]}{\prod_{i=1}^n (r_i-l_i+1)}\)

dp转移的复杂度为\(O(n^3)\),但是由于\(len(j)\)比较大而\(i-k+1\)比较小.,求组合数需要\(O(n)\)的时间枚举。总复杂度为\(O(n^4)\)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define mod 998244353
#define maxn 1000
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k){
    ll ans=1;
    while(k){
        if(k&1) ans=ans*x%mod;
        x=x*x%mod;
        k>>=1;
    }
    return ans;
}
inline ll inv(ll x){
    return fast_pow(x,mod-2);
}
ll fact[maxn+5],invfact[maxn+5],invx[maxn+5];
void ini_inv(int n){
    fact[0]=1;
    for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
    invfact[n]=inv(fact[n]);
    for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
    for(int i=1;i<=n;i++){
        invx[i]=invfact[i]*fact[i-1]%mod;
    }
}
inline ll C(ll n,ll m){
    ll ans=1;
    for(int i=n;i>=n-m+1;i--) ans=ans*i%mod;
    for(int i=1;i<=m;i++) ans=ans*invx[i]%mod;
    return ans;
}
int n;
struct seg{
    int l;
    int r;
}a[maxn+5];
int dn=0;
int tmp[maxn+5];//离散化用
void discrete(){
    dn=0;
    for(int i=1;i<=n;i++){
        tmp[++dn]=a[i].l;
        tmp[++dn]=a[i].r+1;//转成开区间
    }
    sort(tmp+1,tmp+1+dn);
    dn=unique(tmp+1,tmp+1+dn)-tmp-1;
    for(int i=1;i<=n;i++){
        a[i].l=lower_bound(tmp+1,tmp+1+dn,a[i].l)-tmp;
        a[i].r=lower_bound(tmp+1,tmp+1+dn,a[i].r+1)-tmp;
    }
}

ll dp[maxn+5][maxn+5];//dp[i][j]表示前i个数,第i个数在j及之后区间内的方案数
                     //枚举放在第j个区间里的个数,dp[i][j]+=dp[k-1][j+1]*C 

ll sum[maxn+5];

int main(){
    ini_inv(maxn);
    scanf("%d",&n);
    ll all=1;
    for(int i=1;i<=n;i++){
        scanf("%d %d",&a[i].l,&a[i].r);
        all=all*(a[i].r-a[i].l+1)%mod;
    }
    discrete();
    for(int j=1;j<=dn;j++) dp[0][j]=1;
    for(int i=1;i<=n;i++){
        for(int j=a[i].l;j<a[i].r;j++){
            for(int k=i;k>0;k--){
                if(j<a[k].l||j>=a[k].r) break;
                dp[i][j]+=dp[k-1][j+1]*C(i-k+tmp[j+1]-tmp[j],i-k+1)%mod;
                //插板法,把n个物品分成m份,允许有空,C(n+m-1,m)
                //也就是说把r-l+1分成i-k+1份,再加上l,就可以保证在[l,r]内
                dp[i][j]%=mod;
            }
        }
        for(int j=dn-1;j>=1;j--){
            dp[i][j]+=dp[i][j+1];
            dp[i][j]%=mod;
        }
    }
    printf("%lld\n",dp[n][1]*inv(all)%mod);
}

原文地址:https://www.cnblogs.com/birchtree/p/12246317.html

时间: 2024-10-27 19:43:45

[Codeforces 1295F]Good Contest(DP+组合数学)的相关文章

Codeforces 466D Increase Sequence(dp+组合数学)

题目链接:Codeforces 466D Increase Sequence 题目大意:给定一个序列,现在可以选中一段区间,使得整段区间上每个位置数加1,要求最后每个位置都为h,并且选中的区间不能有相同l或则r. 解题思路:因为每个位置最多有一个起始和一个终止(区间). ai和ai+1差的绝对值超过1,则肯定是不行的, ai+1?ai=1,那么一定要从i+1的位置新起一段区间 ai+1?ai=?1,那么一定要在i+1的位置上终止一段区间,C(1ai) ai+1?ai=0,可以不变,也可以终止并新

CodeForces 18E Flag 2 dp

题目链接:点击打开链接 #include<stdio.h> #include<iostream> #include<string.h> #include<set> #include<vector> #include<map> #include<math.h> #include<queue> #include<string> #include<stdlib.h> #include<a

Codeforces 41D Pawn 简单dp

题目链接:点击打开链接 给定n*m 的矩阵 常数k 下面一个n*m的矩阵,每个位置由 0-9的一个整数表示 问: 从最后一行开始向上走到第一行使得路径上的和 % (k+1) == 0 每个格子只能向或走一步 求:最大的路径和 最后一行的哪个位置作为起点 从下到上的路径 思路: 简单dp #include <cstdio> #include <algorithm> #include<iostream> #include<string.h> #include &

CodeForces 19B Checkout Assistant dp

题目链接:点击打开链接 #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <iostream> #include <map> #include <set> #include <math.h> using namespace std; #define inf 115292150460684697

Codeforces 451D Count Good Substrings(组合数学)

题目链接:Codeforces 451D Count Good Substrings 题目大意:定义good string,就是就一个字符串的连续相同字符用一个该字符替代后,形成回文串的字符串.现在给出一个字符串,问说该字符串的子串中,为good string的串有多少个,分长度为奇数和偶数的输出. 解题思路:因为字符串的组成为a和b,所以只要是头尾相同的子串都是满足的.所以我们计算在奇数和偶数位置的奇数个数和偶数个数即可,然后用组合数学求出答案. #include <cstdio> #inc

Neko and Aki&#39;s Prank CodeForces - 1152D (括号序列,dp)

大意: 将所有长度为2*n的合法括号序列建成一颗trie树, 求trie树上选出一个最大不相交的边集, 输出边集大小. 最大边集数一定不超过奇数层结点数. 这个上界可以通过从底层贪心达到, 所以就转化为求奇数层结点数. 然后就dp求出前$i$为'('比')'多j个的方案数, 奇数层且合法的时候统计一下贡献即可. #include <iostream> #include <iostream> #include <algorithm> #include <cstdio

codeforces 148D 【概率dp】

题目链接: codeforces 148D Bag of mice 题意:一个包里面有w只白老鼠和b只黑老鼠,公主与龙依次从包中拿老鼠,每次取一只,当龙拿时还会从包中溜走一只,先拿到老鼠的获胜,当背包中没老鼠时且之前没人拿到白老鼠则龙获胜,问公主获胜的概率是多少. 题解: 设dp[i][j]为背包中有i只白老鼠j只黑老鼠时公主获胜的概率 则公主获胜的情况分成三种: 1.直接拿到白老鼠 p1=i/(i+j) 2.公主拿到黑老鼠,龙拿到黑老鼠,逃跑一只黑老鼠 p2=(j/(i+j)) ((j-1)/

codeforces 500D - New Year Santa Network (树形DP+组合数学)

题目地址:http://codeforces.com/contest/500/problem/D 这题是要先求出每条边出现的次数,然后除以总次数,这样期望就求出来了.先用树形DP求出每个边左右两端总共有多少个点,然后用组合数学公式就可以推出来了. 代码如下: #include <iostream> #include <string.h> #include <math.h> #include <queue> #include <algorithm>

codeforces 204A A. Little Elephant and Interval(dp+组合数学)

题目链接: codeforces 204A 题目大意: 给出一个l和r,求取在l和r之间的首尾相同的数的个数. 题目分析: 按位进行统计,计算出不大于某一个数的所有的合法的情况.然后可以利用这个前缀和求取区间和. 按位统计的时候,首先特判数的长度为1位和两位的情况,分别是10和9,如果当前数就是1位,那么就是这个数的大小,其他具体细节见代码. 然后就是统计所有不足位的情况,也就是数的长度不到给定数长度的情况,不足位的数一定小于给定数,所以直接固定首尾,结果加上10n?2即可. 足位的情况,就是枚