传送门
给定一个包含 n n n 个元素的集合 P = { 1 , 2 , 3 , . . . , n } P=\{1,2,3,...,n\} P={1,2,3,...,n},求有多少个集合 A ⊆ P A⊆P A⊆P,满足任意 x ∈ A x∈A x∈A 有 2 x ∉ A 2x∉A 2x∈/A,且对于 A A A 在 P P P 中的补集 B B B,也满足任意 x ∈ B x∈B x∈B 有 2 x ∉ B 2x∉B 2x∈/B。
有 q q q 个询问,每次给出一个整数 m m m,询问大小为 m m m 的集合有多少个。
答案对 10000019 10000019 10000019 取模。
数据范围: n , m ≤ 1 0 18 n,m≤10^{18} n,m≤1018, q ≤ 100000 q≤100000 q≤100000。
这道题在考场上只想到了 O ( n ) O(n) O(n) 的做法,拿了 80 80 80 分。
我们以 n = 10 n=10 n=10 为例来讲一下这道题吧。
我们把这十个数分成若干条链,每个数都与它的两倍相连,得到下图。
那么根据题意,相邻的两个点中必须选一个。
我们设链长为 l l l。对于一条长度为偶数的链,它对 A A A 集合有 l 2 \frac l 2 2l 的贡献,有两种选择情况。对于一条长度为奇数的链,可以把链头取出来,剩下的就是偶链的情况了,而且链头可以随意选 A A A 或者 B B B。
假设我们有 c 1 c_1 c1 条偶链, c 2 c_2 c2 条奇链,且当前询问的是 m m m,那么答案就是:
a n s = 2 c 1 ( c 2 m − c 1 2 ) ans=2^{c_1}\binom{c_2}{m-\frac{c_1}2} ans=2c1(m−2c1c2)
由于模数 P P P 比较小,可以用 lucas 定理来解决上式的组合数。
现在的问题就是怎么统计奇链和偶链的数量。
我们不妨枚举链长,那么对于一个长度为 l l l 的链,如果链头是 x x x,那么有:
{ x ⋅ 2 l − 1 ≤ n x ⋅ 2 l > n \begin{cases} x\cdot 2^{l-1}\le n\\ x\cdot 2^l>n \end{cases} {x⋅2l−1≤nx⋅2l>n
解出来就是 n 2 l < x ≤ n 2 l − 1 \frac{n}{2^l}<x\le \frac{n}{2^{l-1}} 2ln<x≤2l−1n,我们只需要这个范围内的奇数,分类讨论以下即可。
PS:这道题不好讲清楚,可以自己想一想,捋捋思路。
#include
#include
#include
#define P 10000019
#define ll long long
using namespace std;
ll n,sum=0,A=0;
int q,temp=1,fac[P],ifac[P];
int add(int x,int y) {return x+y>=P?x+y-P:x+y;}
int dec(int x,int y) {return x-y< 0?x-y+P:x-y;}
int mul(int x,int y) {return 1ll*x*y%P;}
int power(int a,int b,int ans=1){
for(;b;b>>=1,a=mul(a,a))
if(b&1) ans=mul(ans,a);
return ans;
}
int prework(){
fac[0]=fac[1]=1;
for(int i=2;i<P;++i) fac[i]=mul(fac[i-1],i);
ifac[P-1]=power(fac[P-1],P-2);
for(int i=P-2;~i;--i) ifac[i]=mul(ifac[i+1],i+1);
}
int C(int n,int m){
if(n<m) return 0;
return mul(fac[n],mul(ifac[m],ifac[n-m]));
}
int Lucas(ll n,ll m){
if(!m) return 1;
return mul(C(n%P,m%P),Lucas(n/P,m/P));
}
ll calc(ll l,ll r) {return (r-l+1)/2+((l&1)&&(r&1));}
int Query(ll x){
if(x<A||x>A+sum) return 0;
return mul(temp,Lucas(sum,x-A));
}
int main(){
prework();
scanf("%lld%d",&n,&q);
for(int i=1;(1ll<<(i-1))<=n;++i){
ll l=n/(1ll<<i)+1,r=n/(1ll<<(i-1)),num=calc(l,r);
A+=(i/2)*num,(i&1)?sum+=num:temp=mul(temp,power(2,num%(P-1)));
}
ll x;
while(q--){
scanf("%lld",&x);
printf("%d\n",Query(x));
}
return 0;
}