zoukankan      html  css  js  c++  java
  • [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] imes 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
    ",dp[n][1]*inv(all)%mod);
    }
    
  • 相关阅读:
    造出最好的 CMS 轮子
    搭建开发框架Express,实现Web网站登录验证
    QueryOver<T>
    NVelocity
    .NET 相依性注入
    Unity 3.5
    java socket 的参数选项解读(转)
    换种方式去分页(转)
    上海市居住证办理材料及流程
    java动态代理
  • 原文地址:https://www.cnblogs.com/birchtree/p/12246317.html
Copyright © 2011-2022 走看看