【BZOJ 5019】[Snoi2017]遗失的答案

题目链接

题目描述

小皮球在计算出答案之后,买了一堆皮肤,他心里很开心,但是一不小心,就忘记自己买了哪些皮肤了。==|||万
幸的是,他还记得他把所有皮肤按照1~N来编号,他买来的那些皮肤的编号(他至少买了一款皮肤),最大公约数
是G,最小公倍数是L。现在,有Q组询问,每组询问输入一个数字X,请你告诉小皮球,有多少种合法的购买方案中
,购买了皮肤X?因为答案太大了,所以你只需要输出答案mod1000000007即可。

Sol

一道很好的计数题

我们先来考虑没有必选的限制怎么做
首先 L L L 肯定是 G G G 的倍数 ,否则无解 , 那么我们可以考虑把问题转化
L / = G , N / = G L/=G,N/=G L/=G,N/=G 这样问题就变成了 从 1 ∼ N 1 \sim N 1N 选出一些最大公约数是 1 的数 , 使得他们的 L C M LCM LCM L L L

(似乎并没有简单些什么…)

我们考虑怎么来达成这种条件 , 首先注意到选择的数一定会是 L L L 的约数 , 不然 L C M LCM LCM就不会是 L L L , 这样我们选数的范围就变少了 , 因为约数个数不会非常多
但是爆搜还是肯定过不去的

我们需要再次考虑一下 G C D 和 L C M GCD 和 LCM GCDLCM的本质
假设我们把 L L L 质因数分解, G G G在这里就已经看作是 1 了可以不管:
L = ∏ i = 1 k p i q i L=\prod_{i=1}^{k} p_i^{q_i} L=i=1kpiqi

那么考虑选出来的数们:
a j = ∏ i = 1 k p i q i j a_j=\prod_{i=1}^{k} p_i^{q_{i_j}} aj=i=1kpiqij

我们可以发现这样一个很直观的东西:
G C D = ∏ i = 1 k m i n { p i q i j } GCD=\prod_{i=1}^k min\{ p_i^{q_{i_j}}\} GCD=i=1kmin{piqij}
L C M = ∏ i = 1 k m a x { p i q i j } LCM=\prod_{i=1}^k max\{ p_i^{q_{i_j}}\} LCM=i=1kmax{piqij}

到这里就非常好写了 , 因为这样的意思就是说 , 对于任意一个 L L L的质因子必须满足,在选出来的数中 , 至少有一个数没有包含该质因子 , 至少要有一个数包含了该质因子的最高次项

而108 以内的数包含的质因数个数最多 只有 8 个 , 所以直接状压一个16长度的二进制状态表示这两个条件对于某质因子是否满足就可以 dp 了呢

但是有必选限制怎么办呢?

直接前后各做一遍 dp 然后拼起来最后把当前必选数加入就行了 , 但是合并似乎要用到 FWT 的或卷积 暴力卷起来就复杂度不对了 , 本人太菜了根本不会FWT , 所以只能放弃这个做法…

我们发现这个东西就是相当于 d ( L ) d(L) d(L) 个数中选出一些数表示的状态或起来使得状态是全集的方案数 , 发现二进制位数很少 , 我们想到了容斥

那么可以直接设, F ( S ) F(S) F(S) 表示选出一些数或成的数是 S 的子集的方案数 , 这样我们就可以容斥求出没有限制情况下的方案数了

那么怎么求 F ( S ) F(S) F(S) 呢 , 这个太简单了 , 因为要或出 S 的子集,首先得保证你自己就是 S 的子集 , 而且满足这个条件之后随便或都是满足的 , 所以 F ( S ) F(S) F(S) 就是 2 的本来就是 S 子集的数的个数 的次方 ,即 F ( S ) = 2 c n t T ,      T ∈ S F(S)=2^{cnt_T} ,\;\; T\in S F(S)=2cntT,TS

那么接下来考虑 必选限制
我们发现必选之后 , 我们接下来选的数只需要满足能把它这个状态中的 0 给全部补成 1 就可以了,比方说这个数表示的状态是 11000011 11000011 11000011 ,那么我们的目标要或出的集合就是 00111100 00111100 00111100的超集

怎么求这个呢?
现在我们 的目标是 00111100 00111100 00111100 的超集 , 由于如果我们或出全集一定是合法的 , 所以我们应该还是要枚举全集的一些子集 , 但肯定不是都要计算

那么要算哪一些呢

由于我们只关心要或出来的缺少的1 , 其他的不用管
那么方案应该是 把剩下的1全部或出来的方案 - 至少一个没有或出来的方案 + 至少两个没有或出来的方案 - …

而我们的 F ( S ) F(S) F(S) 表示的是或出 S S S 的子集的方案 , 所以除开要考虑的位 , 其他的位都是 1 就可以了 , 就用这些 F F F 来进行容斥 , 容斥系数直接和上面一样算也可以 , 手推一下就可以发现是一样的

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
#include<set>
#include<map>
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
const int N=805,mod=1e9+7,MAXN=1<<16;
int n,G,L,Q;
int si[N],num[N],cnt=0,cur=0;
int UP;
map<int,int> Ans;
inline int getsta(int X){
    int state=0;
    for(int i=1;i<=cnt;++i) {
        if(X%si[i]==0) {
            int tot=1;X/=si[i];
            while(X%si[i]==0) ++tot,X/=si[i];
            if(tot==num[i]) state|=1<<i-1;
        }
        else state|=1<<i+cnt-1;
    }
    return state;
}
struct frac {
    int data,state;
    inline bool operator <(frac b)const{return data<b.data;}
    inline void init(int X) {data=X;state=getsta(X);return;}
}T[N];
int gcd(int a,int b){return b? gcd(b,a%b):a;}
int ans[N],Cnt[MAXN],bin[MAXN],high;
inline void upd(int &x,int y){x+=y;if(x>=mod) x-=mod;return;}
inline void dec(int &x,int y){x-=y;if(x<0) x+=mod;}
#define lowbit(a) ((a)&(-a))
inline int Bitcount(int x){int ret=0;for(;x;x-=lowbit(x),++ret);return ret;}
inline void Bitout(int x){
    for(int i=high-1;~i;--i) putchar(((x>>i)&1)+'0');putchar(' ');
}
inline int solve(){
    cnt=0;int ed=sqrt(L),R=L;
    for(int i=2;i<=sqrt(L);++i) {
        if(L%i==0) {
            si[++cnt]=i;num[cnt]=1;L/=i ; 
            while(L%i==0) ++num[cnt],L/=i;
        }
    }
    bin[0]=1;
    for(int i=1;i<MAXN;++i) bin[i]=bin[i-1],upd(bin[i],bin[i]);
    if(L!=1) si[++cnt]=L,num[cnt]=1;
    high=cnt<<1;UP=1<<high;
    for(int i=1;i<=ed&&i<=n;++i) if(R%i==0) {T[++cur].init(i);if(R/i!=i&&R/i<=n) T[++cur].init(R/i);}
    sort(T+1,T+1+cur);for(int i=1;i<=cur;++i) ++Cnt[T[i].state];
    for(int i=0;i<high;++i) for(int s=0;s<UP;++s) if(s&(1<<i)) {Cnt[s]+=Cnt[s^(1<<i)];}
    int ans=0;for(int s=0;s<UP;++s) {(Bitcount(s)&1)? dec(ans,bin[Cnt[s]]):upd(ans,bin[Cnt[s]]);}
    return ans;
}
inline int solve(int x)
{
    if(Ans.count(x)) return Ans[x];
    int pos=lower_bound(T+1,T+1+cur,(frac){x,0})-T;
    if(T[pos].data!=x) return 0;
    register int state=T[pos].state;register int inv=(UP-1)^state;
    int ans=0;
    for(register int s=inv;~s;s=(s-1)&inv) {register int T=s|state; (Bitcount(T)&1)? dec(ans,bin[Cnt[T]-1]):upd(ans,bin[Cnt[T]-1]); if(!s) break;}
    return Ans[x]=ans;
}
int main()
{
    n=read();G=read();L=read();Q=read();
    bool fl=0;int R=L;
    if(L%G!=0) fl=1;L/=G;n/=G;
    solve();
    int a;
    for(int i=1;i<=Q;++i) {
        a=read();
        if(fl||(a%G!=0)||(R%(a/G)!=0)) puts("0");
        else printf("%d\n",solve(a/G));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值