[ARC104E] Random LIS 题解
[ARC104E] Random LIS 题解
Link
吐了,一下午就写了这一个题……主要是题解都说的很草率。然后上课的时候貌似讲的方法不是很能做(也许是我太菜了),总之我得写篇题解整理整理。
首先 \(n\) 很小,可以直接爆搜所有相对大小,即我们去搜索 \(1\) 到 \(n\) 的排名,排名可以一样(即 \(a_i\) 相等)。最长上升子序列直接暴力 dp;然后,我们可以根据这个排名以及各个点的值域确定出每个排名对应的值域上界(就是最小的那个)。每个排名所对应的值应该是严格单调递增的,这样,我们可以对这个排名进行 dp。设 $f_{i,j} $ 表示第 \(i\) 个排名选择第 \(j\) 个值域区间的方案数,这里的值域可以离散化。我们首先枚举第 \(i\) 个排名,然后枚举这个排名选择的值域区间 \(j\),由于可能一个值域区间能放很多个排名,所以我们还要枚举一个左端点 \(k\),表示 \([k, i]\) 中的排名全部从 \(j\) 中取,由于单调递增,所以相当于是从长度为 \(len\) 的区间内选择 \(i-k+1\) 个数。最后,我们枚举排名 \(k-1\) 选择的值域区间,转移即可。
方程:$ f_{i, j} = \sum_{k = 1}^{i} \sum_{o = 0}^{j-1} {len_j \choose i-k+1}f_{k-1, o} $
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 8, mod = 1e9+7;
inline int fpow(int a, int b){
int ret = 1;
a%=mod;
while(b){
if(b & 1){
ret = (1ll*ret*a)%mod;
}
b>>=1;
a = (1ll*a*a)%mod;
}
return ret;
}
int a[N], b[N], na[N];
int inv[N], fac[N];
inline int C(int n, int m){
if(n<0 || m<0 || n<m) return 0;
int ret = 1;
for(int i = n; i>=n-m+1; --i) ret = (1ll*ret*i)%mod;
return 1ll*ret*inv[m]%mod;
}
int n, nn;
void prework(){
fac[0] = 1;
for(int i = 1; i<=n; ++i){
fac[i] = (1ll*fac[i-1]*i)%mod;
}
inv[n] = fpow(fac[n], mod-2);
for(int i = n-1; i>=0; --i){
inv[i] = (1ll*inv[i+1]*(i+1))%mod;
}
}
bool vis[N];
int p[N];//相对位次
int dp[N];
int mx;
int f[N][N];//表示第 i 个位次位于值域第 j 段的方案数。
int h[N];//每一个位次的上界
inline int calc(){
memset(h, 0x3f, sizeof(h));
int m = 0;
for(int i = 1; i<=n; ++i){
h[p[i]] = min(h[p[i]], a[i]);
m = max(m, p[i]);
}
int tot = 0;
for(int i = 1; i<=m; ++i){
na[++tot] = h[i];
}
sort(na+1, na+tot+1);
tot = unique(na+1, na+tot+1)-na-1;
for(int i = 1; i<=m; ++i){
h[i] = lower_bound(na+1, na+tot+1, h[i])-na;
}//离散化
memset(f, 0, sizeof(f));
f[0][0] = 1;
for(int i = 1; i<=m; ++i){
for(int j = 1; j<=h[i]; ++j){//枚举当前点选的值域
int mn = h[i];
for(int k = i; k>=1; --k){//枚举这个值域控制的范围(左端点)
mn = min(h[k], mn);
if(j > mn) break;
for(int o = 0; o<j; ++o){
if(!f[k-1][o]) continue;
f[i][j] = (1ll*f[i][j]+1ll*C(na[j]-na[j-1], i-k+1)*f[k-1][o]%mod)%mod;
}
}
}
}
int ret = 0;
for(int i = 1; i<=tot; ++i){
ret = (1ll*ret+f[m][i])%mod;
}
return ret;
}
int ans = 0;
void dfs(int pos, int lim){
if(pos>lim){
for(int i = 1; i<=n; ++i){
b[i] = p[i];
}
sort(b+1, b+n+1);
for(int i = 1; i<=n; ++i){
if(b[i]-b[i-1]>1) return;
}
mx = 0;
for(int i = 1; i<=n; ++i){
dp[i] = 1;
for(int j = 1; j<i; ++j){
if(p[i]>p[j]) dp[i] = max(dp[i], dp[j]+1);
}
mx = max(dp[i], mx);
}//dp求最长上升子序列
ans = (1ll*ans+1ll*mx*calc()%mod)%mod;
return;
}
for(int i = 1; i<=n; ++i){
p[pos] = i;
dfs(pos+1, lim);
}
}
signed main(){
scanf("%d", &n);
prework();
for(int i = 1; i<=n; ++i){
scanf("%d", &a[i]);
}
dfs(1, n);
int innv = 1;
for(int i = 1; i<=n; ++i){
innv = (1ll*innv*a[i])%mod;
}
innv = fpow(innv, mod-2);
ans = 1ll*ans*innv%mod;
ans = (ans+mod)%mod;
printf("%d\n", ans);
return 0;
}