[组合计数题单][CTSC2017]吉夫特

真就题目不知道怎么取,就把 \(\sf gitf\) 给硬翻呗= =.

壹、题目

传送门 to LUOGU

题目其实就是问你从 \(n\) 个数的数组 \(a\) 中选一个不上升的子序列,记作 \(t\),这个子序列满足

\[\prod_{i=2}^k{t_i\choose t_{i-1}}\bmod 2>0 \]

也就是左边那一坨是一个奇数,问你这个 \(k\) 个数的数组 \(t\) 有多少种选法。

贰、题解

2.1.前一步化简

首先有 \(\tt Lucas\) 定理

\[{n\choose m}\equiv{n\bmod p\choose m\bmod p}\times {\left\lfloor\frac{n}{p}\right\rfloor\choose \left\lfloor\frac{m}{p}\right\rfloor}\pmod p \]

对于 \(\bmod 2\) 的情况,右边是 \({\left\lfloor\frac{n}{2}\right\rfloor\choose \left\lfloor\frac{m}{2}\right\rfloor}\),实际上就是右移一,而 \({n\bmod 2\choose m\bmod 2}\) 实际上就四种情况,分别是 \({0\choose 0},{0\choose 1},{1\choose 0},{1\choose 1}\),而只有 \(0\choose 1\)\(0\),其他都是 \(1\)所以这种情况不能出现,,结合以上,实际上就是,\(n\) 的二进制表达下是 \(m\) 的超集时,满足 \(n\choose m\) 为奇数

\(A\)\(B\) 的超集,即 \(B\)\(A\) 的子集,即 \(A\cap B=B\)\(B\subseteq A\).

对于这个题而言,我们就可以简化题目为:

\(n\) 个数的数组 \(a\) 中选一个不上升的子序列,并且这个满足子序列后一项的二进制是前一项的超集,这样的子序列的个数。

然后,就有两种做法。

2.2.枚举子集

这种做法比较好实现,但是时间复杂度并非最优秀。

由于每个数都互不相同,考虑定义 \(f_i\) 表示以 \(i\) 开头的子序列数量,从后往前转移,转移的时候枚举子集即可,而我们都在枚举子集了,从后往前转移则一定保证整个序列不上升,直接暴力转就可以了。

时间复杂度就是枚举子集的复杂度,\(\mathcal O(3^{\log \max\{a_i\}})\),算出来大概 \(1e8\sim 2e8\) 左右,看来这个题目的限制完全就是给这种方法设计的啊......

2.3.类分块思想

针对 \(\tt 2.2.\) 的一个经典优化 虽然我是第一次见到罢了,诶好像以前见到过

考虑定义 \(g(i,j)\) 表示满足二进制下前九位刚好是 \(i\),后九位是 \(j\) 的子集的所有 \(a\)\(f_a\) 之和,从后往前枚举一个数的时候,对于这个数 \(x\),如果是计算答案,则枚举前九位的子集,如果是转移,则枚举后九位的超集。

至于枚举超集,我们可以先枚举补集的子集,最后将原集与补集取交。

最后答案就是 \(\sum_{i=0}^{2^{9}-1}g_{i,2^{9}-1}\),时间复杂度为 \(\mathcal O(6^{\log \max\{a_i\}\over 2})\).

至于为什么要去前九位和后九位呢?由于 \(a_i\le 233333\),则最大的 \(a_i\) 的二进制也只有 \(18\) 位。

叁、代码

事实证明 \(\tt 2.3.\) 的复杂度比 \(\tt 2.2.\) 不知道优秀到哪里去了......具体来说,一个 \(228ms\),一个 \(3.3s\)......

\(\tt 2.2.\) 的代码:好省就是不多快......

using namespace Elaina;

const int mod=1e9+7;
const int maxn=211985;
const int maxa=233333;

int f[maxa+5],n,a[maxn+5];

inline void input(){
	n=readin(1);
	rep(i,1,n)a[i]=readin(1);
}

inline void getf(){
	int ans=0;
	// pay attention to the index, here the index is a[i] instead of i
	fep(i,n,1){
		f[a[i]]=1;
		for(int j=(a[i]-1)&a[i];j;--j&=a[i]){
			f[a[i]]=(f[a[i]]+f[j])%mod;
		}
		ans=(ans+f[a[i]])%mod;
	}
	writc(ans-n,'\n');
}

signed main(){
	input();
	getf();
	return 0;
}

\(\tt 2.3.\) 的代码:多快就是不好省......

using namespace Elaina;

const int mod=1e9+7;
const int maxn=211985;
const int maxa=233333;
const int maxsize=1<<9;

int n,a[maxn+5],g[maxsize+5][maxsize+5];

inline void input(){
	n=readin(1);
	rep(i,1,n)a[i]=readin(1);
}

inline void getf(){
	int block=1<<9,S=(1<<9)-1;
	fep(t,n,1){
		int pre=a[t]/block;
		int suf=a[t]%block;
		int val=1;
		for(int i=pre;i;--i&=pre)
			val=(val+g[i][suf])%mod;
		val=(val+g[0][suf])%mod;
		int rest=S^suf;
		for(int i=rest;i;--i&=rest)
			g[pre][i^suf]=(g[pre][i^suf]+val)%mod;
		g[pre][suf]=(g[pre][suf]+val)%mod;
	}
	int ans=0;
	rep(i,0,S)ans=(ans+g[i][S])%mod;
	writc((ans+mod-n)%mod,'\n');
}

signed main(){
	input();
	getf();
	return 0;
}

肆、用到の小 \(\tt trick\)

\(\tt Lucas\) 定理,内容即

\[{n\choose m}\equiv{n\bmod p\choose m\bmod p}\times {\left\lfloor\frac{n}{p}\right\rfloor\choose \left\lfloor\frac{m}{p}\right\rfloor}\pmod p \]

特别地,对于一个组合数 \(n\choose m\),它为奇数当且仅当 \(n\) 的二进制是 \(m\) 的超集。

另外,特殊的优化,对于二进制,可以考虑用类分块的思想,将前半部分的二进制和后半部分的二进制分开,这个 \(\tt trick\) 还没怎么见过,具体还不是十分清楚,先体会体会罢。

posted @ 2021-02-15 16:15  Arextre  阅读(55)  评论(0编辑  收藏  举报