[SNOI2017]遗失的答案

220 篇文章 2 订阅
30 篇文章 0 订阅

题目

传送门 to luogu

思路

其实只要 对于每个质因数,次数搞定了就行。

题外话:这个转化并不容易想到吧……我想到它完全是因为这是我们的 F W T \tt FWT FWT 专题中的题目。

用四进制数 S S S 表示, gcd ⁡ \gcd gcd 搞定否和 l c m \rm lcm lcm 搞定否。则
f i ( S ) = f i − 1 ( S ) + f i − 1 ( S ∪ g i ) f_i(S)=f_{i-1}(S)+f_{i-1}(S\cup g_i) fi(S)=fi1(S)+fi1(Sgi)
这里用 g i g_i gi 表示 i i i 可以满足哪些条件。虽说有的数字是绝对不能选的……

先别管其他的事儿了。这里 S S S 的范围太大了 ! ! ! !!! !!! 怎么办?

假如 gcd ⁡ \gcd gcd l c m \rm lcm lcm 指数相同,那么可以去掉,因为没得选。

假如 gcd ⁡ \gcd gcd l c m \rm lcm lcm 指数差 1 1 1 ,这倒也可以。那么 l c m \rm lcm lcm 至少就是 gcd ⁡ \gcd gcd p p p 倍。取 gcd ⁡ = 1 \gcd=1 gcd=1 也最多只有 8 8 8 p p p ,因为
2 × 3 × 5 × 7 × 11 × 13 × 17 × 19 × 23 = 223 , 092 , 870 2\times 3\times 5\times 7\times 11\times 13\times 17\times 19\times 23=223,092,870 2×3×5×7×11×13×17×19×23=223,092,870
已经超过了 1 0 8 10^8 108 的范围限制啦!于是 S S S 被压缩到了 2 16 2^{16} 216 这么大!

此时 n n n 个数字并不是很重要,它们可以根据效用分成 S + 1 S+1 S+1 类(其中一类是不能选的有害垃圾)。具体而言——枚举 L L L 的因数就可以啦!复杂度仅仅 O ( 8 L log ⁡ L ) \mathcal O(8\sqrt{L}\log L) O(8L logL) 而已!

不妨设每一类的个数为 a i a_i ai ,那么我们只需要选几个类,使得它们的按位或为 t t t ,此时方案数为 ∏ ( 2 a i − 1 ) \prod (2^{a_i}-1) (2ai1) ,就是选这个类中的若干个嘛。

那么我们用分治?欲求出 v ∈ [ 0 , 2 n ) v\in [0,2^n) v[0,2n) 的方案数,只需求出 v ∈ [ 0 , 2 n − 1 ) v\in[0,2^{n-1}) v[0,2n1) 能凑出啥, v ∈ [ 2 n − 1 , 2 n ) v\in[2^{n-1},2^{n}) v[2n1,2n) 能凑出啥,然后拼起来。

不难发现递归时最高位被确定了!所以问题规模确实在缩小。合并可以用 F W T \tt FWT FWT 作按位或卷积。所以复杂度是
T ( n ) = 2 T ( n 2 ) + O ( n log ⁡ n ) = O ( n log ⁡ 2 n ) T(n)=2T\left({n\over 2}\right)+\mathcal O(n\log n)=\mathcal O(n\log^2n) T(n)=2T(2n)+O(nlogn)=O(nlog2n)

其中 n n n 最大为 2 16 2^{16} 216 ,还挺快的。但是跑偏了……

回到问题上:必须选 X X X 怎么满足?考虑找到 X X X 所在的类别(效用等价类),然后强迫不选这个类别。这是那个经典问题了,跟这道题需要搞定的问题是一样的。也用分治?如果单点加入是直接加入的话,每次加入就高达 S S S 的复杂度,恐怕不行。

那么区间加入必须一起加入。考虑把每个分治时得到的,类似线段树区间的,凑出每种状态的方案数 ⟨ b ⟩ \langle b\rangle b 都存下来?空间大小为 O ( S log ⁡ S ) \mathcal O(S\log S) O(SlogS) 。然后可以硬性 O ( N log ⁡ N ) \mathcal O(N\log N) O(NlogN) 吗?显然不是的!因为上面的更长,需要将就上面的!

能不能把上面的削短?因为我们想要的只有全集。譬如,这一步我往右走,说明这一位选 0 0 0 1 1 1 并不重要——必须选的这一位是 1 1 1 呢!往左,则相反,这里必须选 1 1 1 。所以我们可以把这一位砍掉!

此时,复杂度应当为
T ( N ) = 2 T ( N 2 ) + O ( N log ⁡ N ) = O ( N log ⁡ 2 N ) T(N)=2T\left({N\over 2}\right)+\mathcal O(N\log N)=\mathcal O(N\log^2N) T(N)=2T(2N)+O(NlogN)=O(Nlog2N)

啊哈!搞定了!不过你大概也猜的到代码有多难打。

虽说常数可能不会太优秀(因为有很多复制、乘法等操作),但是并不慢。

代码

由于每个区间存储的 ⟨ b ⟩ \langle b\rangle b 是残缺的(省略了前面几位)并且没有考虑区间内一个不选的情况,要特别思考一下这东西。 F W T \tt FWT FWT 做完后,有可能需要再加一份乘数。

s o l v e solve solve 中的 b b b 可能不需要加。因为 b b b 实际上代表的是前面有一些 0 0 0 的状态,不能加入。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int Mod = 1e9+7;
const int inv2 = (Mod+1)>>1;

inline int qkpow(int_ b,int q){
	int a = 1;
	for(; q; q>>=1,b=b*b%Mod)
		if(q&1) a = a*b%Mod;
	return a;
}

void FWT(int a[],int n,int opt){
	for(int i=0; i<n; ++i)
	for(int j=0; j<(1<<n); ++j)
	if(j>>i&1){
		a[j] = (a[j]+opt*a[j^(1<<i)])%Mod;
		if(a[j] < 0) a[j] += Mod;
	}
}

const int MaxN = 16;
int sxy[1<<MaxN], zxy[1<<MaxN];
/** @brief Set c = sxy \times zxy */
void mul(int c[],int n){
	FWT(sxy,n,1), FWT(zxy,n,1);
	for(int i=0; i<(1<<n); ++i)
		c[i] = 1ll*zxy[i]*sxy[i]%Mod;
	FWT(c,n,-1); // get result
}

/** @brief a += b ( len = 1 << n ) */
void add(int a[],int b[],int n){
	for(int i=0; i<(1<<n); ++i)
		a[i] = (a[i]+b[i])%Mod;
}

int b[MaxN+1][1<<MaxN];
int g[1<<MaxN]; // calc (1 << cnt)
void prepare(int l,int r,int dep){
	if(r-l == 1){
		g[l] = qkpow(2,g[l]);
		return void(b[dep][l] = g[l]-1);
	}
	int m = (l+r)>>1; // middle
	prepare(l,m,dep-1);
	prepare(m,r,dep-1);
	/* sxy = LEFT, zxy = RIGHT */ ;
	memcpy(sxy,b[dep-1]+l,(r-l)<<2);
	memset(sxy+m-l,0,(r-m)<<2);
	memcpy(zxy,b[dep-1]+l,(r-l)<<2);
	memset(zxy,0,(m-l)<<2);
	/* b = zxy * sxy + zxy + sxy */ ;
	mul(b[dep]+l,dep);
	add(b[dep]+l,b[dep-1]+l,dep);
}

int now[MaxN+1][1<<MaxN];
int fuck_xez; // very right point
int ans[1<<MaxN];
void solve(int l,int r,int dep){
	if(r-l == 1){
		if(g[l] == 1) // no such thing
			return ; // forget it
		ans[l] = 1ll*g[l]*inv2%Mod;
		ans[l] = 1ll*ans[l]*now[dep][0]%Mod;
		ans[l] = (ans[l]+Mod)%Mod;
		return ;
	}
	int m = (l+r)>>1; // middle
	/* Go to left part */ ;
	memcpy(sxy,now[dep],(r-l)<<2);
	memcpy(zxy,b[dep-1]+l,(r-l)<<2);
	memset(zxy,0,(m-l)<<2);
	mul(now[dep-1],dep); // now * b
	add(now[dep-1],now[dep],dep); // + now
	if(r == fuck_xez) // + b
		add(now[dep-1]+m-l,b[dep-1]+m,dep-1);
	memcpy(now[dep-1],now[dep-1]+m-l,(m-l)<<2);
	solve(l,m,dep-1);
	/* Go to right part */ ;
	memcpy(sxy,now[dep],(r-l)<<2);
	memcpy(zxy,b[dep-1]+l,(r-l)<<2);
	memset(zxy+m-l,0,(r-m)<<2);
	mul(now[dep-1],dep); // now * b
	add(now[dep-1],now[dep],dep); // + now
	if(r == fuck_xez) // + b
		add(now[dep-1],b[dep-1]+l,dep-1);
	add(now[dep-1],now[dep-1]+m-l,dep-1);
	solve(m,r,dep-1);
}

vector< int > ys, cnt;
int add(int x){
	int S = 0, len = ys.size();
	for(int i=0; i<len; ++i){
		int p = 0;
		for(; x%ys[i]==0; ++p)
			x /= ys[i];
		if(p == 0) // gcd
			S |= (1<<(i<<1));
		if(p == cnt[i])
			S |= (1<<(i<<1|1));
	}
	return S;
}
int main(){
	int n = readint(); int N = n;
	int G = readint(); int GG = G;
	int L = readint(); int LL = L;
	bool bad = (L%G != 0);
	L /= G, n /= G; // only k*G
	G = L; // save archive
	/* Sieve factor of L */ ;
	int len = 0; // ys.size()
	for(int i=2; i<=L/i; ++i)
		if(L%i == 0){
			ys.push_back(i);
			cnt.push_back(0);
			for(; L%i==0; L/=i)
				++ cnt[len];
			++ len;
		}
	if(L != 1){
		ys.push_back(L);
		cnt.push_back(1);
		++ len;
	}
	/* Calculate size of each type */ ;
	for(int i=1; i<=G/i&&i<=n; ++i)
		if(G%i == 0){ // G = lcm'
			++ g[add(i)];
			if(G/i <= n && i != G/i)
				++ g[add(G/i)];
		}
	/* Prepare b in advance */ ;
	fuck_xez = 1<<len<<len;
	prepare(0,(1<<len<<len),len<<1);
	/* Solve all answers */ ;
	solve(0,(1<<len<<len),len<<1);
	/* Tackle all queries */ ;
	int q = readint();
	for(int x; q; --q){
		x = readint();
		if(bad || x > N || LL%x || x%GG){
			puts("0"); continue;
		}
		if(LL == GG){
			printf("%d\n",x == GG);
			continue;
		}
		printf("%d\n",ans[add(x/GG)]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值