Portal --> 出错啦qwq(好吧其实是没有)

Description

  给定两个正整数\(n,k\),选择一些互不相同的正整数,满足这些数的最小公倍数恰好为\(n\),并且这些数的和为\(k\)的倍数

  求选择的方案数对\(232792561\)取模

  数据范围:多组数据,组数\(T<=10,n<=10^{18},k<=20\),且\(n\)的所有质因子不大于\(100\)

Solution

  这题。。好神仙啊qwq敲爆脑子都想不出来系列qwq

  注意到\(n<=10^{18}\)意味着\(n\)至多有\(15\)个不同的质因子(前\(15\)个质数乘一下就知道了),并且\(k\)的范围也是比较小的

  然后还有一点就是这个模数比较有趣,它\(=lcm(1,2,...,20)+1\),也就是说它可以对长度在\(1..20\)的区间DFT

​  那不管别的我们可以先考虑一个最朴素的dp

  记\(f[i][j]\)表示当前所选的数状态为\(i\),和\(\%k=j\)的方案数,其中这个“所选数的状态”具体计算方式是:假设写成分解质因数的形式后\(n=\sum p_i^{mi_i}\),其中\(p_i\)为\(n\)的每一个不同的质因子,我们的状态是一个二进制数,并且二进制的每一位对应一个\(n\)的质因子,对于二进制的第\(i\)位(假设对应\(p_i\)),如果说当前所选的数中存在一个数\(x\)满足\(p_i^{mi_i}|x\),那么这位为\(1\),否则为\(0\)

​  这样一来,我们最后的答案就应该是\(f[(1<<Cnt)-1][0]\),其中\(Cnt\)为\(n\)的不同质因子个数

  至于求解。。分解质因数和预处理因数可以暴力求解,但是后面的dp直接暴力转移的话稳稳的T啊(光枚举子集就要\(3^{Cnt}\)了还要再乘个\(k\)),所以现在的问题是我们要怎么比较快速地转移

​  观察这个dp两维的转移,我们会发现第一维的转移可以写成一个集合或卷积的形式(或起来等于某个数就把值累计进去),而第二维的转移则直接枚举因数什么的大力转移就好了

  然后这里有一个很有意思的想法:首先我们发现这两维如果可以分开处理就会比较好一点,然后注意到这个模数很有趣,允许我们进行区间长度\(\in[1,20]\)的DFT,而DFT操作之后,dp的这两维在某种意义上就是独立的了,换句话来说,如果说我们先在同行内转移\(f\)数组,再对转以后每行的不完全计算的\(f\)数组做一次DFT,这行的\(f\)的第二维就不会互相影响了,也就是说我们可以通过这种方式实现第一维和第二维转移的分离

  这样我们就可以先处理\(f[][i]\),直观一点来说就是先在同行转移\(f\)数组,具体一点就是枚举\(n\)的因数,然后考虑加进去的贡献,也就是假设当前枚举到的因数是\(a[i]\),\(a[i]\)对应的状态是\(st\),那么我们可以对于所有的\(j\in [0,k)\)转移:

\[f[st][(j+a[i]\%k)]+=f'[st][j]
\]

​  之所以在后面的\(f\)打了个\('\)是因为我们这里要累加进去的是用\(a[i]\)更新前的\(f[st]\)的版本

​  

​  这样我们就得到了整合了同行数据之后的\(f\)数组(为了防止弄混在接下来的描述中我们还是将这个不完全转移的\(f\)数组记为\(f1\)好了),然后对于每一行DFT一下,接下来就是考虑第一维的转移了,更加直观一点就是。。同列的\(f\)进行转移

​  接下来为了让描述变得更加简洁,我们将第二维省去(因为反正转移的时候都是同列转移)

​  现在我们要做的事情就是挑出若干个\(f1[i]\),满足\(i_1\ or\ i_2\ or\ ...\ i_m=st\)然后将这堆\(f1[i]\)的值累加到\(f[st]\)去

  我们记\(F(S)=\sum\limits_{i\subseteq S}f[i]\),如果说我们知道了\(F(S)\)的取值,那么只要大力容斥一下就可以得出\(f\)数组了,具体一点的话就是:

\[f[T]=\sum\limits_{S\subseteq T}(-1)^{|T|-|S|}F(S)
\]

  我们将\(F(S)\)进行一下转化,会发现:

\[\begin{aligned}
F(S)&=\sum\limits_{j\subseteq S}f[j]\\
&=\prod\limits_{i\subseteq S}(1+f1[i])
\end{aligned}
\]

  具体的话就是因为对于\(f\)来说,每一个\(f[j]\)都是由若干个\(f1[i]\)组成的,我们考虑将第二个等号后面的连乘的括号拆掉,展开之后会发现囊括了\(i\)的所有组合方式(为了方便理解可以自己用比较小的规模模拟一下),然后因为我们限制了\(i\subseteq S\)所以可以保证任意的组合方式都是满足组合后\(\subseteq S\)的

​  然后由于\(f1\)是已知的,所以我们现在就要想如何快速求\(F(S)\)

  显然枚举子集不现实

​  如果我们直接从小到大枚举状态,每次将这个状态对应的\(f1\)值转移的话,可能会出现重复计算的情况(比如说\(010\)可以转移到\(011\)和\(110\),而\(011\)和\(110\)又都可以转移到\(111\),这样如果按照这种转移方式的话,\(111\)这里\(010\)的\(f1\)值就会被重复计算),所以这里考虑一种很玄学的按顺序转移方式:

​  我们考虑二进制一位一位转移,从低位枚举到高位,每枚举一位就把所有的这位为\(0\)的状态的值累加到将这位修改为\(1\)后的状态里面去,具体一点的话就是:

​  我们以\(Cnt=3\)为例子,状态转移大概是这样:

  其中橙黄色的箭头表示转移的方向,然后位数是从最右边开始数的

​  这样的话显然所有的状态都能转移到它能转移到的位置,现在我们来说明一下为什么这样不会重复计算:首先可以确认的一点是,这种重复计算的情况只会出现在转移到一个被修改了两个及以上位的状态的时候,而我们这样的枚举方式每次只转移到修改了一位的地方,并且是按照数位从低到高,状态从小到大的顺序转移,比如说刚才提到的\(010\)在\(111\)中被重复计算的情况,放在这个转移方式中就应该是:在转移第一位的时候,\(010\)会先转移到\(011\),然后在转移第三位的时候再由累加了\(010\)的\(011\)转移到\(111\),每次都是从只修改一位的地方转移过来,不会出现重复计算的情况(但其实上面的证明也不算。。特别严谨。。还是感性理解既视感qwq)

​  然后我们就可以在\(O(2^{Cnt})\)的时间内求得\(F(S)\),接下来直接大力容斥一下就可以得出\(f[(1<<Cnt)-1][0]\)的值最后IDFT一下就可以得出答案啦

  

  代码大概长这个样子

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int MOD=232792561,G=71,N=110;
const int p[26]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97};
ll mi[N],rec_mx[N],Tmp[N],have[N];
ll a[500010];
int f[(1<<15)+10][20];
ll n,K,T,Cnt,all;
void add(int &x,int y){x=(1LL*x+y+MOD)%MOD;}
bool in(int st,int x){return st>>(x-1)&1;}
int St(int x){return 1<<x-1;}
int ksm(int x,int y){
int ret=1,base=x;
for (;y;y>>=1,base=1LL*base*base%MOD)
if (y&1) ret=1LL*ret*base%MOD;
return ret;
}
namespace DFT{/*{{{*/
int tmp[110],W[1010][2];
int inv;
void init(){
int rt=ksm(G,(MOD-1)/K);
W[0][0]=1;
for (int i=1;i<=1000;++i)
W[i][0]=1LL*W[i-1][0]*rt%MOD;
for (int i=0;i<=1000;++i)
W[i][1]=ksm(W[i][0],MOD-2);
}
void dft(int *a,int n,int op){
for (int i=0;i<n;++i) tmp[i]=0;
for (int i=0;i<n;++i)
for (int j=0;j<n;++j)
tmp[i]=(1LL*tmp[i]+1LL*a[j]*W[i*j][op==-1]%MOD)%MOD;
if (op==-1){
inv=ksm(n,MOD-2);
for (int i=0;i<n;++i) tmp[i]=1LL*tmp[i]*inv%MOD;
}
for (int i=0;i<n;++i) a[i]=tmp[i];
}
}/*}}}*/
void Div(ll x){
for (int i=1;i<=25;++i)
if (x%p[i]==0){
++Cnt; rec_mx[Cnt]=1; mi[Cnt]=0; have[Cnt]=p[i];
while (x%p[i]==0)
x/=p[i],++mi[Cnt],rec_mx[Cnt]*=p[i];
}
}
void dfs(int now,ll prod){
if (now>Cnt){
a[++a[0]]=prod;return;
}
ll tmp=prod;
for (int i=0;i<=mi[now];++i){
dfs(now+1,tmp);
tmp*=have[now];
}
}
void prework(){
a[0]=0; Cnt=0;
Div(n);
dfs(1,1);
}
void update(int st,int r){
for (int i=0;i<K;++i) Tmp[i]=f[st][i];
for (int i=0;i<K;++i) add(f[st][(i+r)%K],Tmp[i]);
}
void solve(){
int st,op;
memset(f,0,sizeof(f));
all=(1<<Cnt)-1;
for (int i=0;i<=all;++i) f[i][0]=1;//init
for (int i=1;i<=a[0];++i){
st=0;
for (int j=1;j<=Cnt;++j)
if (a[i]%rec_mx[j]==0) st|=St(j);
update(st,a[i]%K);
}
for (int i=0;i<=all;++i)
DFT::dft(f[i],K,1); for (int i=1;i<=Cnt;++i)
for (int j=0;j<=all;++j)
if (in(j,i))
for (int k=0;k<K;++k)
f[j][k]=1LL*f[j][k]*f[j^St(i)][k]%MOD;
for (int i=0;i<all;++i){
op=1;
for (int j=1;j<=Cnt;++j)
if (!in(i,j)) op*=-1;
for (int j=0;j<K;++j)
add(f[all][j],op*f[i][j]);
}
DFT::dft(f[all],K,-1);
printf("%d\n",f[all][0]);
} int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%d",&T);
for (int o=1;o<=T;++o){
scanf("%lld%lld",&n,&K);
DFT::init();
prework();
solve();
}
}

【2018北京集训(六)】Lcm的更多相关文章

  1. 【2018北京集训6】Lcm DFT&FWT

    首先我们来看下此题的模数232792561. 232792561=lcm(1,2,3.......20)+1.这个性质将在求值时用到. 我们将n分解质因数,令$m$为$n$的素因子个数,设n=$\Pi ...

  2. 【2019北京集训六】路径(path) 二分+DP

    此题niubi! 题目大意:给你一颗n个点的点带权无根树,现在请您进行以下两步操作: 1,选择一个$[0,T]$之间的整数$C$,并令所有的点权$wi$变为$(wi+C)%MOD$ 2,选择若干条点不 ...

  3. 【2018北京集训十二】 coin 矩阵快速幂

    矩阵快速幂原来还可以这么用?? 你们城里人还真会玩. 我们令$f[i][j][k]$表示总的钱数为i,当前使用的最大面值硬币的面值为$v_j$,最小为$v_k$的方案数量. 不难发现$f[i][j][ ...

  4. 【北京集训D2T3】tvt

    [北京集训D2T3]tvt \(n,q \le 1e9\) 题目分析: 首先需要对两条路径求交,对给出的四个点的6个lca进行分类讨论.易于发现路径的交就是这六个lca里面最深的两个所形成的链. 然后 ...

  5. 牛客2018国庆集训 DAY1 D Love Live!(01字典树+启发式合并)

    牛客2018国庆集训 DAY1 D Love Live!(01字典树+启发式合并) 题意:给你一颗树,要求找出简单路径上最大权值为1~n每个边权对应的最大异或和 题解: 根据异或的性质我们可以得到 \ ...

  6. (2016北京集训十)【xsy1528】azelso - 概率期望dp

    北京集训的题都是好题啊~~(于是我爆0了) 注意到一个重要的性质就是期望是线性的,也就是说每一段的期望步数可以直接加起来,那么dp求出每一段的期望就行了... 设$f_i$表示从$i$出发不回到$i$ ...

  7. 【2017 北京集训 String 改编版】子串

    题意 你有一个字符串,你需要支持两种操作: 1:在字符串的末尾插入一个字符 \(c\) 2:询问当前字符串的 \([l,r]\) 子串中的不同子串个数 为了加大难度,操作会被加密(强制在线). \(n ...

  8. 【2016北京集训测试赛(十六)】 River (最大流)

    Description  Special Judge Hint 注意是全程不能经过两个相同的景点,并且一天的开始和结束不能用同样的交通方式. 题解 题目大意:给定两组点,每组有$n$个点,有若干条跨组 ...

  9. 2016北京集训测试赛(十六)Problem C: ball

    Solution 这是一道好题. 考虑球体的体积是怎么计算的: 我们令\(f_k(r)\)表示\(x\)维单位球的体积, 则 \[ f_k(1) = \int_{-1}^1 f_{k - 1}(\sq ...

随机推荐

  1. eclipse以MapReduce本地模式运行程序

    1.准备好所需的文件winutils.exe.hadoop-eclipse-plugin-2.7.3.jar.hadoop-common-2.2.0-bin-master.zip 下载路径:http: ...

  2. Windows本地上传源码到Gitee远程仓库

    1.下载Git,并安装. 安装时一路默认即可 https://git-scm.com/downloads 验证Git安装成功否 cmd 下输入,出现版本号即成功 git --version 2.生成s ...

  3. exec命令详解

    基础命令学习目录首页 原文链接: exec: 在bash下输入man exec,找到exec命令解释处,可以看到有”No new process is created.”这样的解释,这就是说exec命 ...

  4. fs - 文件系统

    fs 模块提供了一些 API,用于以一种类似标准 POSIX 函数的方式与文件系统进行交互. 用法如下: const fs = require('fs'); 所有的文件系统操作都有异步和同步两种形式. ...

  5. 后端程序员必备的Linux基础知识

    我自己总结的Java学习的系统知识点以及面试问题,目前已经开源,会一直完善下去,欢迎建议和指导欢迎Star: https://github.com/Snailclimb/Java-Guide > ...

  6. Daily Scrum (2015/11/7)

    今晚谢金洛同学的UI工作完成,我们进行了UI和后端的拼接,准备开始规范化地进行系统测试. 成员 今日任务及成果 时间 明日任务 符美潇 1.把之前PM分配的编码任务及其说明准备好发给PM 1h 待定 ...

  7. 20172313『Java程序设计』课程结对编程练习_四则运算第二周阶段总结

    20172313『Java程序设计』课程结对编程练习_四则运算第二周阶段总结 结对伙伴 20172326康皓越 博客地址(http://www.cnblogs.com/326477465-a/p/90 ...

  8. 剑指offer:矩形覆盖

    题目描述: 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形.请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 解题思路: 和跳台阶那道题差不多.分别以矩形的两条边长做拓 ...

  9. maven导入项目时出现“Cannot read lifecycle mapping metadata …… invalid END header (bad central directory offset)pom”错误的解决方法

    出现该错误是因为jar包版本不匹配,比如linux上的jar包导入到windows上了.可以将.m2\repository的org.apache.maven.plugins删掉然后让maven重新下载 ...

  10. Openresty+Lua+Redis灰度发布

    灰度发布,简单来说,就是根据各种条件,让一部分用户使用旧版本,另一部分用户使用新版本.百度百科中解释:灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式.AB test就是一种灰度发布方式,让一部分 ...