题意:
n n n个范围 [ l i , r i ] , a i ∈ [ l i , r i ] [l_i,r_i],a_i\in[l_i,r_i] [li,ri],ai∈[li,ri],求所有符合限制的单调不增序列的元素和的总和。
解题思路:
因为是a是单调不降的,所以他们区间的左端点应该是单调不降的,右端点是单调不增的。可以分区间讨论。
2n个点划分出2n个区间,然后用
d
p
(
i
,
j
)
dp(i,j)
dp(i,j)表示考虑了前i个区间,选择了j个数字的符合条件的序列的元素和。用
f
(
i
,
j
)
f(i,j)
f(i,j)表示前i个区间选了j个数字的方案数,那么枚举有多少个数在当前区间范围内即可转移。
怎么转移呢?
区间有x个数字,可重复选出y个数字组成单调不增序列的方案数,可以转换为有x个桶,放y个小球的方案数。那么用隔板法我们知道方案数为
C
(
x
+
y
−
1
,
x
−
1
)
C(x+y-1, x-1)
C(x+y−1,x−1).
这样前面的总和要乘上这么多的方案数。然后再看当前新加的数字的贡献:因为是所有方案的总和,所以可以取平均,在
[
l
,
r
]
[l,r]
[l,r]选一个数字相当于贡献
(
r
+
l
)
/
2
(r+l)/2
(r+l)/2。那么选
y
y
y个对
d
p
(
i
,
j
)
dp(i,j)
dp(i,j)的额外贡献为:
单
个
数
字
贡
献
∗
数
字
个
数
∗
前
面
区
间
选
取
的
方
案
数
∗
当
前
区
间
可
以
选
取
的
方
案
数
单个数字贡献*数字个数* 前面区间选取的方案数 * 当前区间可以选取的方案数
单个数字贡献∗数字个数∗前面区间选取的方案数∗当前区间可以选取的方案数
其中单个数字贡献为
(
r
+
l
)
/
2
(r+l)/2
(r+l)/2
数字个数为
y
y
y
前面区间选取方案为
f
(
i
−
1
,
j
−
y
)
f(i-1,j-y)
f(i−1,j−y)
当前区间选取方案数用隔板法算一下
这样就得到了
d
p
(
i
,
j
)
dp(i,j)
dp(i,j)的转移。
f
(
i
,
j
)
f(i,j)
f(i,j)的转移就是
当
前
区
间
方
案
数
∗
之
前
区
间
方
案
数
当前区间方案数*之前区间方案数
当前区间方案数∗之前区间方案数 的累加了。
具体看代码吧:
细节:
- 隔板法算出来的组合数不要预处理,直接算就行了,因为数字个数很少。
- 在计算 d p dp dp的转移的时候与区间端点做乘之前先模,不然爆long long(63行)
#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
/*注意爆long long*/
const int maxn = 205;
ll cc[200];
int num = 0;
int n;
ll l[55], r[55];
const ll mod = 998244353;
ll dp[200][200];
ll f[200][200];
ll fac[maxn], ifac[maxn], inv[maxn];
ll qm(ll a, ll b){ll res = 1; while(b) {if(b&1) res=res*a%mod; a = a*a%mod; b>>=1;} return res;}
ll Choose(ll a, ll b){//a个可选的数字,b个要放的数 C(b+a-1, b)
ll res = 1;
for(ll i = a; i < b+a; ++i) res = i%mod*res%mod;
for(ll i = 2; i <= b; ++i) res = res*inv[i]%mod;
return res;
}
int main()
{
fac[0] = ifac[0] = 1;
for(int i = 1; i < maxn; ++i) fac[i] = fac[i-1]*i%mod, ifac[i] = qm(fac[i], mod-2), inv[i] = qm(i, mod-2);
cin>>n;
for(int i = 1; i <= n; ++i){
scanf("%lld", &l[i]); l[i] = max(l[i], l[i-1]);
cc[++num] = l[i];
//cout<<"L:"<<l[i]<<endl;
}
for(int i = 1; i <= n; ++i){
scanf("%lld", &r[i]);
//cc[++num] = r[i]+1;
//cout<<"R:"<<r[i]+1<<endl;
}
cc[++num] = r[n]+1;
for(int i = n-1; i >= 1; --i){
r[i] = min(r[i], r[i+1]);
cc[++num] = r[i]+1;
}
sort(cc+1,cc+1+num);
num = unique(cc+1,cc+1+num)-cc-1;
assert(num < 200);
ll inv2 = (mod+1)/2;
f[0][0] = 1;
for(int i = 1; i < num; ++i){
dp[i][0] = 0;
f[i][0] = 1;
ll L = cc[i], R = cc[i+1]-1;
//cout<<"L:"<<L<<" R:"<<R<<endl;
for(int j = 1; j <= n; ++j){
dp[i][j] = dp[i-1][j];
f[i][j] = f[i-1][j];
for(int k = j; k > 0; --k){
if(l[k] <= L && R <= r[k]){
//cout<<"i:"<<i<<" j:"<<j<<" k:"<<k<<" L:"<<L<<" R:"<<R<<endl;
dp[i][j] = (dp[i][j] +
dp[i-1][k-1]*Choose(R-L+1, j-k+1)%mod +
f[i-1][k-1]*Choose(R-L+1, j-k+1)%mod*(R%mod+L%mod)%mod*inv2%mod*(j-k+1)%mod )%mod;
f[i][j] = (f[i][j] + f[i-1][k-1]*Choose(R-L+1, j-k+1)%mod)%mod;
//cout<<"dp:"<<dp[i][j]<<endl;
}else break;
}
// cout<<"i:"<<i<<"j:"<<j<<" dp:"<<dp[i][j]<<endl;
}
}
//cout<<dp[1][1]<<endl;
ll ans = dp[num-1][n];
//assert(ans > 0);
cout<<ans<<endl;
}
/*
2
1 2
3 4
*/