• ARC104E


    Portal

    给定 (n) 个正整数 (a_i)。构造一个长度为 (n) 的序列 (x),使 (x_i)([1,a_i]capmathbb Z) 内均匀随机。问 (x) 的 LIS 的期望。

    (nin[1,6])

    看完题面满脸不会做,然后看数据范围咋这么小。。

    考虑枚举 (x_{1sim n}) 的相对大小,因为是 (nleq6) 所以不会太多。不难想到,枚举它们的相对大小关系,就是枚举排完序的 (x')(1sim n) 分别是原 (x) 中的第几个。然而我现场没有想到有 (leq) 这种东西,直接枚举全排列了,导致最后没写出来。事实上,它们中可能有相等的,那么比如 (1,2,(3,4,5),6)(1,2,(4,5,3),6) 这两种就是一样的,会重复计算。于是我们需要改进一下枚举方法。

    不难想到,这样就相当于,每一种状态先极大相等段分解,然后再考虑相等段之间的相对大小关系。也就是先将 (n) 个数不重不漏分成若干组,然后对组进行全排列。这个方案数好像叫啥 Fubini Number,(n=6) 时只有 (4683)(720 o 4683),悲)。不知道有没有啥好的枚举方法,我写的是枚举每个数的排名(可相同),然后对于每一种方案判合法(排名 (1sim mx) 是否全出现过,其中 (mx) 是当前最大排名)。

    然后考虑对于每一种相对大小关系,算出它对答案的贡献。显然同一种相对大小关系的 LIS 是固定的,这个随便 DP 就可以求出。那么我们只需要算出当前相对大小关系下 (x) 具体取值的方案数即可,最后与 LIS 乘起来再加到答案里最后除以总方案数即可得到期望值。

    考虑如何求方案数。不难想到将 (a) 离散化,然后作为断点把整个值域分成几段,这样考虑起来方便许多。考虑对于某一种(就目前而言合法的)每个数所在段的情况,显然所在段不同即可直接肯定的比出大小,如果相同的话则有几种情况。对于每个段,我们数一下里面坐落着几个相等段,它们肯定不能互相相等,大小关系又是确定的,于是我们只需要在当前段中随意取出这么多个不同的数,排个序即可得到恰好一种方案。于是乘几个组合数(组合数 (dbinom xy) 虽然 (x) 很大,但是 (y)(mathrm O(n)) 的,所以可以 (mathrm O(n)) 飞快算)即可得到当前每个数所在段的情况的方案数。然后枚举每个数所在段的情况,加一下即可。

    全是爆搜,复杂度未知,不过如果能保证第二次爆搜是「极好」的(即每次 x==mx_id+1 的时候都一定是合法方案,这是很容易做到的),那么跑得飞快。

    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    const int mod=1000000007;
    int qpow(int x,int y){
    	int res=1;
    	while(y){
    		if(y&1)res=1ll*res*x%mod;
    		x=1ll*x*x%mod;
    		y>>=1;
    	}
    	return res;
    }
    int inv(int x){return qpow(x,mod-2);}
    const int N=6;
    int n;
    int fact[]={1,1,2,6,24,120,720},factinv[]={inv(1),inv(1),inv(2),inv(6),inv(24),inv(120),inv(720)};
    int a[N+1];
    vector<int> nums;
    int ans;
    int id[N+1],mx_id;
    int sum;
    int in_for_id[N+1],in[N+1];
    int len(int x){return x==0?nums[x]:nums[x]-nums[x-1];}
    int comb(int x,int y){
    	if(x<y)return 0;
    	int res=1;
    	for(int i=x-y+1;i<=x;i++)res=1ll*res*i%mod;
    	return 1ll*res*factinv[y]%mod;
    }
    void dfs0(int x=1){
    	if(x==mx_id+1){
    		for(int i=1;i<=n;i++)in[i]=in_for_id[id[i]];
    		int prod=1;
    		for(int i=1;i<=n;i++)if(nums[in[i]]>a[i])return;
    		int cnt[N]={};
    		for(int i=1;i<=mx_id;i++)cnt[in_for_id[i]]++;
    		for(int i=0;i<nums.size();i++)prod=1ll*prod*comb(len(i),cnt[i])%mod;
    		(sum+=prod)%=mod;
    //		for(int i=1;i<=n;i++)cout<<in[i]<<" ";printf("|| %d
    ",prod);
    		return;
    	}
    	for(int i=in_for_id[x-1];i<nums.size();i++){
    		in_for_id[x]=i;
    		dfs0(x+1);
    	}
    }
    void dfs(int x=1){
    	if(x==n+1){
    		vector<int> v;
    		for(int i=1;i<=n;i++)v.pb(id[i]);
    		sort(v.begin(),v.end());
    		v.resize(unique(v.begin(),v.end())-v.begin());
    		if(v.back()!=v.size())return;
    		mx_id=v.size();
    		int dp[N+1],mx=0;
    		for(int i=1;i<=n;i++){
    			dp[i]=1;
    			for(int j=1;j<i;j++)if(id[j]<id[i])dp[i]=max(dp[i],dp[j]+1);
    			mx=max(mx,dp[i]);
    		}
    		sum=0;
    		dfs0();
    //		for(int i=1;i<=n;i++)cout<<id[i]<<" ";puts("");cout<<mx<<" "<<sum<<"!
    ";
    		(ans+=1ll*mx*sum%mod)%=mod;
    		return;
    	}
    	for(int i=1;i<=n;i++){
    		id[x]=i;
    		dfs(x+1);
    	}
    }
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++)cin>>a[i],nums.pb(a[i]);
    	sort(nums.begin(),nums.end());
    	nums.resize(unique(nums.begin(),nums.end())-nums.begin());
    	dfs();
    	int tot=1;
    	for(int i=1;i<=n;i++)tot=1ll*tot*a[i]%mod;
    	cout<<1ll*ans*inv(tot)%mod;
    	return 0;
    }
    

    然后看了 official solution 发现第二部分的那个爆搜可以有多项式复杂度解法?其实就是一个并不难的 DP。这个 DP 的精髓在哪里呢,它是如何把非多项式变成多项式的呢?注意到在某个相对大小关系下,考虑第 (i) 大的相等段的时候,只需要保证比第 (i-1) 大的相等段大即可,这样一来就有了阶段性。

    所以也难怪,这的确是个比较弱智的 DP。很多 DP 都是这样,感受最明显的是状压 DP,先想当前状态与哪些量无关,哪些量有关,想办法搞出阶段性,然后就好 DP 了。

    具体说就是 (dp_{i,j}) 表示考虑到第 (i) 大的相等段,第 (i) 大的相等段坐落在第 (j) 段的方案数。由于小于分成在同段和异段两种,所以需要枚举与 (i) 同段的极大相等段区间左端点,乘个组合数转移,这样是三方或者四方的,不管了。

    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    const int inf=INT_MAX,mod=1000000007;
    int qpow(int x,int y){
    	int res=1;
    	while(y){
    		if(y&1)res=1ll*res*x%mod;
    		x=1ll*x*x%mod;
    		y>>=1;
    	}
    	return res;
    }
    int inv(int x){return qpow(x,mod-2);}
    const int N=6;
    int n;
    int fact[]={1,1,2,6,24,120,720},factinv[]={inv(1),inv(1),inv(2),inv(6),inv(24),inv(120),inv(720)};
    int a[N+1];
    vector<int> nums;
    int ans;
    int id[N+1],mx_id;
    int len(int x){return x==0?nums[x]:nums[x]-nums[x-1];}
    int comb(int x,int y){
    	if(x<y)return 0;
    	int res=1;
    	for(int i=x-y+1;i<=x;i++)res=1ll*res*i%mod;
    	return 1ll*res*factinv[y]%mod;
    }
    int sol(){
    	int dp[N+1][N+1]={},lim[]={inf,inf,inf,inf,inf,inf,inf};
    	for(int i=1;i<=n;i++)lim[id[i]]=min(lim[id[i]],a[i]);
    	for(int i=0;i<=nums.size();i++)dp[0][i]=1;
    	for(int i=1;i<=mx_id;i++){
    		for(int j=0;j<nums.size();j++)if(nums[j]<=lim[i])
    			for(int k=i;k;k--){
    				if(nums[j]>lim[k])break;
    				(dp[i][j+1]+=1ll*comb(len(j),i-k+1)*dp[k-1][j]%mod)%=mod;
    			}
    //		cout<<dp[i][0]<<" "<<dp[i][1]<<" "<<dp[i][2]<<"!
    ";
    		for(int j=1;j<=nums.size();j++)(dp[i][j]+=dp[i][j-1])%=mod;
    	}
    //	for(int i=1;i<=n;i++)cout<<id[i]<<" ";printf("|| %d
    ",dp[mx_id][nums.size()]);
    	return dp[mx_id][nums.size()];
    }
    void dfs(int x=1){
    	if(x==n+1){
    		vector<int> v;
    		for(int i=1;i<=n;i++)v.pb(id[i]);
    		sort(v.begin(),v.end());
    		v.resize(unique(v.begin(),v.end())-v.begin());
    		if(v.back()!=v.size())return;
    		mx_id=v.size();
    		int dp[N+1],mx=0;
    		for(int i=1;i<=n;i++){
    			dp[i]=1;
    			for(int j=1;j<i;j++)if(id[j]<id[i])dp[i]=max(dp[i],dp[j]+1);
    			mx=max(mx,dp[i]);
    		}
    		(ans+=1ll*mx*sol()%mod)%=mod;
    		return;
    	}
    	for(int i=1;i<=n;i++){
    		id[x]=i;
    		dfs(x+1);
    	}
    }
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++)cin>>a[i],nums.pb(a[i]);
    	sort(nums.begin(),nums.end());
    	nums.resize(unique(nums.begin(),nums.end())-nums.begin());
    	dfs();
    	int tot=1;
    	for(int i=1;i<=n;i++)tot=1ll*tot*a[i]%mod;
    	cout<<1ll*ans*inv(tot)%mod;
    	return 0;
    }
    
  • 相关阅读:
    spark发现新词
    树的算法总结
    机器学习树的算法总结
    Spark Streaming实例
    ubuntu上通用解压方式
    论MYSQL数据库数据错误的处理
    macOS Sierra上Opencv的安装与使用
    phpstudy2016 redis扩展 windows
    细说PHP7
    正则表达式与.htaccess的配置
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/arc104e-solution.html
Copyright © 2020-2023  润新知