[组合计数题单][CTSC2017]吉夫特
真就题目不知道怎么取,就把 \(\sf gitf\) 给硬翻呗= =.
壹、题目
题目其实就是问你从 \(n\) 个数的数组 \(a\) 中选一个不上升的子序列,记作 \(t\),这个子序列满足
也就是左边那一坨是一个奇数,问你这个 \(k\) 个数的数组 \(t\) 有多少种选法。
贰、题解
2.1.前一步化简
首先有 \(\tt Lucas\) 定理
对于 \(\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\),它为奇数当且仅当 \(n\) 的二进制是 \(m\) 的超集。
另外,特殊的优化,对于二进制,可以考虑用类分块的思想,将前半部分的二进制和后半部分的二进制分开,这个 \(\tt trick\) 还没怎么见过,具体还不是十分清楚,先体会体会罢。