6516. 【GDOI2020模拟03.28】数二数(two)

题目

有一个 [ 1 , n ] [1,n] [1,n]的整数,可以询问 [ L , R ] [L,R] [L,R],表示整个整数是否在这个区间里。
计算有多少个询问集合,使得这些询问过后,无论整数是 [ 1 , n ] [1,n] [1,n]中哪一个都能被唯一确定。
n ≤ 300 n\leq 300 n300

思考历程

简化一下题目大意:可以通过询问,将数字分成若干组。
一开始 [ 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:

  1. Some questions are contained by a removed subarray. There is basically no restriction here
  2. 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的询问集合。

  1. 部分的询问区间是已经被 V V V变成 V ′ V' V的过程中,删去的子区间所包含的。这些询问基本上对 V ′ V' V没有限制。
  2. 除去那些对 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+12j=1i1fjgi,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=gi1,j1+k=0gik2,j12Ck+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;
}

总结

好思维的一道题目……
如果在比赛的时候给我想,我死也想不出来……
论脑子的重要性……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值