【2019/08/24测试 T2】数学课

传送门


Problem

给定一个包含 n n n 个元素的集合 P = { 1 , 2 , 3 , . . . , n } P=\{1,2,3,...,n\} P={1,2,3,...,n},求有多少个集合 A ⊆ P A⊆P AP,满足任意 x ∈ A x∈A xA 2 x ∉ A 2x∉A 2x/A,且对于 A A A P P P 中的补集 B B B,也满足任意 x ∈ B x∈B xB 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,m1018 q ≤ 100000 q≤100000 q100000


Solution

这道题在考场上只想到了 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(m2c1c2)

由于模数 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} {x2l1nx2l>n

解出来就是 n 2 l &lt; x ≤ n 2 l − 1 \frac{n}{2^l}&lt;x\le \frac{n}{2^{l-1}} 2ln<x2l1n,我们只需要这个范围内的奇数,分类讨论以下即可。

PS:这道题不好讲清楚,可以自己想一想,捋捋思路。


Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值