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


    题目

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

    思考历程

    简化一下题目大意:可以通过询问,将数字分成若干组。
    一开始([1,n])为一组。
    某个询问([L,R])之后,将这个东西与当前分成的若干组分别取交,从而进一步分组。
    到最后如果每一组如果仅仅包含一个数字,那么就是一种合法的方案。

    我只会强行状压DP……然而并没有这档部分分。


    正解

    正解是个玄妙的东西。官方题解在此。

    分组的更加严谨的解释:如果两个数字被包含的询问集合完全相同,那么它们就被分到同一组。
    给组别分配一个编号,令(V_i)表示数字(i)在哪个组。
    如果(V_i)互不相同,那么这就是一个合法的方案;如果有至少一个相同,那么就不合法。

    考虑用总方案减去不合法的方案。
    有个奇妙的性质:如果存在四个数字(a,b,c,d)满足(a<b<c<d)(V_a=V_c,V_b=V_d),那么一定有(V_a=V_b=V_c=V_d)
    用人话说就是,每个组别的最左端和最右端形成的区间,不可能真相交
    题解用一种奇妙的方法将(V)缩短。
    假如(V=[1,2,3,2,4,2,5,5,10,6,7,8,7,6,9])
    可以通过以下方式构造出(V')
    (V)中从左往右扫,见到一个组别号(x),首先将(x)丢到(V')的后面,然后找出(l_x)(r_x)分别表示类别为(x)的最左端和最右端,将([l_x,r_x])(V)中删除。
    这个例子构造出来的(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) 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') - we have the first hint of dynamic programming

    本人英语不好,解释得不好也请见谅……
    要搞出(V)(V')之间的联系,先考虑能得到(V)的询问集合。

    1. 部分的询问区间是已经被(V)变成(V')的过程中,删去的子区间所包含的。这些询问基本上对(V')没有限制。
    2. 除去那些对(V')没有用的询问,剩余询问集合应该唯一地确定(V')

    那么可以从(V')推到(V),限制只在新增的那些子区间中。
    (f_i)表示([1,i])合法的方案数。考虑用总数减去不合法的。
    (g_{i,j})表示(V)总长为(i),经过缩减处理的(V')总长为(j)的方案数。这个东西就是(V')(V)新增的限制。

    [f_i=2^{C_{i+1}^2}-sum_{j=1}^{i-1}f_jg_{i,j} ]

    至于(g_{i,j})的转移,考虑在(V')后面新增一个组别编号。分为两种情况:这个组别仅仅出现一次;这个组别出现了至少两次,枚举左端和右端之间空隙的长度(k),在这个长度为(k)的区间里的询问自由组合。

    [g_{i,j}=g_{i-1,j-1}+sum_{k=0}^{}g_{i-k-2,j-1}2^{C_{k+1}^1} ]

    时间复杂度(O(n^3))

    在网上查题解的时候还发现这个方法可以用拉格朗日反演来优化,时间复杂度(O(n lg n))的。
    表示没有看懂,以后心血来潮就来这里看看。


    代码

    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
    ",f[n]);
    	return 0;
    }
    

    总结

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

  • 相关阅读:
    compiere简易介绍及个人看法
    js数字金额转换为英文金额(数字的中英文转化) 修正版
    eval和document.getElementById
    关于netsuite编辑单据页面默认打开某tab的办法
    js 抛出异常 throw
    Training Agenda netsuite
    js 多维数组 应用
    AJAX提交数据时 中文处理 以及js url 中文处理
    监视所有 HTTP 请求和响应的工具 Fiddler工具介绍 (转载)
    关于netsuite创建组合按钮的方法,合并按钮,netsuite API
  • 原文地址:https://www.cnblogs.com/jz-597/p/12607909.html
Copyright © 2020-2023  润新知