题目
有一个
[
1
,
n
]
[1,n]
[1,n]的整数,可以询问
[
L
,
R
]
[L,R]
[L,R],表示整个整数是否在这个区间里。
计算有多少个询问集合,使得这些询问过后,无论整数是
[
1
,
n
]
[1,n]
[1,n]中哪一个都能被唯一确定。
n
≤
300
n\leq 300
n≤300
思考历程
简化一下题目大意:可以通过询问,将数字分成若干组。
一开始
[
1
,
n
]
[1,n]
[1,n]为一组。
某个询问
[
L
,
R
]
[L,R]
[L,R]之后,将这个东西与当前分成的若干组分别取交,从而进一步分组。
到最后如果每一组如果仅仅包含一个数字,那么就是一种合法的方案。
我只会强行状压DP……然而并没有这档部分分。
正解
正解是个玄妙的东西。官方题解在此。
分组的更加严谨的解释:如果两个数字被包含的询问集合完全相同,那么它们就被分到同一组。
给组别分配一个编号,令
V
i
V_i
Vi表示数字
i
i
i在哪个组。
如果
V
i
V_i
Vi互不相同,那么这就是一个合法的方案;如果有至少一个相同,那么就不合法。
考虑用总方案减去不合法的方案。
有个奇妙的性质:如果存在四个数字
a
,
b
,
c
,
d
a,b,c,d
a,b,c,d满足
a
<
b
<
c
<
d
a<b<c<d
a<b<c<d且
V
a
=
V
c
,
V
b
=
V
d
V_a=V_c,V_b=V_d
Va=Vc,Vb=Vd,那么一定有
V
a
=
V
b
=
V
c
=
V
d
V_a=V_b=V_c=V_d
Va=Vb=Vc=Vd
用人话说就是,每个组别的最左端和最右端形成的区间,不可能真相交。
题解用一种奇妙的方法将
V
V
V缩短。
假如
V
=
[
1
,
2
,
3
,
2
,
4
,
2
,
5
,
5
,
10
,
6
,
7
,
8
,
7
,
6
,
9
]
V=[1,2,3,2,4,2,5,5,10,6,7,8,7,6,9]
V=[1,2,3,2,4,2,5,5,10,6,7,8,7,6,9]
可以通过以下方式构造出
V
′
V'
V′:
在
V
V
V中从左往右扫,见到一个组别号
x
x
x,首先将
x
x
x丢到
V
′
V'
V′的后面,然后找出
l
x
l_x
lx和
r
x
r_x
rx分别表示类别为
x
x
x的最左端和最右端,将
[
l
x
,
r
x
]
[l_x,r_x]
[lx,rx]从
V
V
V中删除。
这个例子构造出来的
V
′
=
[
1
,
2
,
5
,
10
,
6
,
9
]
V'=[1,2,5,10,6,9]
V′=[1,2,5,10,6,9]
至于这个东西有什么用,我先放出题解原文:
You might be wondering why is this helpful? Well, it’s helpful because any set of questions that corresponds to V V V should respect the following:
- Some questions are contained by a removed subarray. There is basically no restriction here
- All the other questions should uniquely identify V ′ V' V′ - we have the first hint of dynamic programming
本人英语不好,解释得不好也请见谅……
要搞出
V
V
V和
V
′
V'
V′之间的联系,先考虑能得到
V
V
V的询问集合。
- 部分的询问区间是已经被 V V V变成 V ′ V' V′的过程中,删去的子区间所包含的。这些询问基本上对 V ′ V' V′没有限制。
- 除去那些对 V ′ V' V′没有用的询问,剩余询问集合应该唯一地确定 V ′ V' V′。
那么可以从
V
′
V'
V′推到
V
V
V,限制只在新增的那些子区间中。
设
f
i
f_i
fi表示
[
1
,
i
]
[1,i]
[1,i]合法的方案数。考虑用总数减去不合法的。
g
i
,
j
g_{i,j}
gi,j表示
V
V
V总长为
i
i
i,经过缩减处理的
V
′
V'
V′总长为
j
j
j的方案数。这个东西就是
V
′
V'
V′到
V
V
V新增的限制。
f
i
=
2
C
i
+
1
2
−
∑
j
=
1
i
−
1
f
j
g
i
,
j
f_i=2^{C_{i+1}^2}-\sum_{j=1}^{i-1}f_jg_{i,j}
fi=2Ci+12−j=1∑i−1fjgi,j
至于
g
i
,
j
g_{i,j}
gi,j的转移,考虑在
V
′
V'
V′后面新增一个组别编号。分为两种情况:这个组别仅仅出现一次;这个组别出现了至少两次,枚举左端和右端之间空隙的长度
k
k
k,在这个长度为
k
k
k的区间里的询问自由组合。
g
i
,
j
=
g
i
−
1
,
j
−
1
+
∑
k
=
0
g
i
−
k
−
2
,
j
−
1
2
C
k
+
1
1
g_{i,j}=g_{i-1,j-1}+\sum_{k=0}^{}g_{i-k-2,j-1}2^{C_{k+1}^1}
gi,j=gi−1,j−1+k=0∑gi−k−2,j−12Ck+11
时间复杂度
O
(
n
3
)
O(n^3)
O(n3)。
在网上查题解的时候还发现这个方法可以用拉格朗日反演来优化,时间复杂度
O
(
n
lg
n
)
O(n \lg n)
O(nlgn)的。
表示没有看懂,以后心血来潮就来这里看看。
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define N 310
int n,mo;
ll p2[N*N];
ll f[N],g[N][N];
int main(){
// freopen("in.txt","r",stdin);
freopen("two.in","r",stdin);
freopen("two.out","w",stdout);
scanf("%d%d",&n,&mo);
p2[0]=1;
for (int i=1;i<=(n+1)*n>>1;++i)
p2[i]=p2[i-1]*2%mo;
g[0][0]=1;
for (int i=1;i<=n;++i)
for (int j=1;j<=i;++j){
ll s=g[i-1][j-1];
for (int k=0;i-k-2>=0;++k)
s=(s+g[i-k-2][j-1]*p2[(k+1)*k>>1])%mo;
g[i][j]=s;
}
for (int i=1;i<=n;++i){
ll s=0;
for (int j=1;j<i;++j)
s=(s+f[j]*g[i][j])%mo;
f[i]=(p2[(i+1)*i>>1]-s%mo+mo)%mo;
}
printf("%lld\n",f[n]);
return 0;
}
总结
好思维的一道题目……
如果在比赛的时候给我想,我死也想不出来……
论脑子的重要性……