[THUPC 2023 初赛] 快速 LCM 变换
题目描述
小 I 今天学习了快速最小公倍数变换(Fast Least-Common-Multiple Transform, FLT),于是他想考考你。
给定一个长度为 \(n\) 的正整数序列 \(r_1,r_2,\cdots,r_n\)。你需要做以下操作恰好一次:
- 选择整数 \(i,j\) 使得 \(1 \le i < j \le n\)。在序列末尾加入 \((r_i+r_j)\),并将 \(r_i\) 和 \(r_j\) 从序列中删除。
可以注意到总共有 \(\frac{n(n-1)}{2}\) 种可能的操作,每种操作会得到一个长度为 \(n-1\) 的序列。
你需要对所有的这 \(\frac{n(n-1)}{2}\) 个序列,求出序列中所有元素的最小公倍数,并给出它们的和模 \(998244353\) 的值。
输入格式
输入的第一行包含一个正整数 \(n\),表示序列的长度。接下来一行 \(n\) 个正整数 \(r_1,r_2,\cdots,r_n\),描述初始序列。
输出格式
输出一行一个整数,表示所有序列的最小公倍数的和模 \(998244353\) 的值。
样例 #1
样例输入 #1
3
2 3 4
样例输出 #1
40
提示
样例解释 1
- \(i=1,j=2\) 时,得到的序列为 \(\{4,5\}\),最小公倍数为 \(20\);
- \(i=1,j=3\) 时,得到的序列为 \(\{3,6\}\),最小公倍数为 \(6\);
- \(i=2,j=3\) 时,得到的序列为 \(\{2,7\}\),最小公倍数为 \(14\)。
因此输出为 \(20+6+14=40\)。
子任务
对于所有测试数据,\(2 \le n \le 5 \times 10^5, 1 \le r_1,r_2,\cdots,r_n \le 10^6\)。
题目来源
来自 2023 清华大学学生程序设计竞赛暨高校邀请赛(THUPC2023)初赛。
题解等资源可在 https://github.com/THUSAAC/THUPC2023-Pre 查看。
求最小公倍数还要取模?只能拆成质数之幂再乘起来了。
那么一个质数的哪些幂次最终可能会被计入答案呢?乍一看,次数前三大的都用到。但仔细分析一下,其实只用次数前两大的数。
这是因为,如果同时删掉了前两大次数的数,那么会增加 \(p^{k_1}+p^{k_2}\),那么他一定是 \(p^{k_2}\) 的倍数。
设原来的最小公倍数为 \(s\),那么如果删掉某一个数之后,答案会变成 \(s\) 的多少倍是固定的,也是分开的。我们可以枚举质数,算出删某一个数 \(x\) 时的答案 \(p_x\)。
此时如果我删掉数字 \(x\) 和 \(y\),那么此时 \(s\) 会变成 \(p_xp_ys\),然后我们还要统计 \(x+y\) 时带来的贡献。
可以通过 NTT 计算出选择和为 \(a\) 时的各种方案的 \(s\) 倍数之和,此时要计算 \(a\) 会变成 \(s\) 的多少倍。分解质因数后看他的次数是否超过 \(s\) 的次数就行了。
注意要好好去重。
#include<bits/stdc++.h>
using namespace std;
const int P=998244353,N=2097152,iv3=332748118,iv2=P+1>>1;
int a[N],f[N],p[N],cmx[N],mx[N],r[N],n,nw[N],ret=1,ans,c[N];
vector<int>g[N];
int pown(int x,int y)
{
if(!y)
return 1;
int t=pown(x,y>>1);
if(y&1)
return 1LL*t*t%P*x%P;
return 1LL*t*t%P;
}
void ntt(int a[],int op)
{
for(int i=0;i<N;i++)
if(i<r[i])
swap(a[i],a[r[i]]);
for(int md=1;md<N;md<<=1)
{
int g=pown(op? 3:iv3,(P-1)/(md<<1));
for(int i=0;i<N;i+=md<<1)
{
int pw=1;
for(int j=0;j<md;j++,pw=1LL*pw*g%P)
{
int ax=pw*1LL*a[i+j+md]%P;
a[i+j+md]=(a[i+j]-ax+P)%P;
(a[i+j]+=ax)%=P;
}
}
}
}
int main()
{
// freopen("P9135_1.in","r",stdin);
for(int i=2;i<N;i++)
if(!g[i].size())
for(int j=1;j*i<N;j++)
g[i*j].push_back(i);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",a+i),f[a[i]]=1,c[a[i]]++;
for(int i=1;i<N;i++)
r[i]=r[i>>1]>>1|((i&1)*N/2);
for(int i=1;i<=n;i++)
{
for(int j=0;j<g[a[i]].size();j++)
{
int cnt=0,k=a[i];
while(k%g[a[i]][j]==0)
++cnt,k/=g[a[i]][j];
if(cnt>mx[g[a[i]][j]])
{
cmx[g[a[i]][j]]=mx[g[a[i]][j]],mx[g[a[i]][j]]=cnt;
nw[g[a[i]][j]]=a[i];
}
else if(cnt>cmx[g[a[i]][j]])
cmx[g[a[i]][j]]=cnt;
}
}
for(int i=1;i<N;i++)
if(mx[i])
f[nw[i]]=1LL*f[nw[i]]*pown(pown(i,mx[i]-cmx[i]),P-2)%P,ret=1LL*ret*pown(i,mx[i])%P;
for(int i=0;i<N;i++)
p[i]=f[i]*1ll*c[i]%P;
ntt(p,1);
for(int i=0;i<N;i++)
p[i]=1LL*p[i]*p[i]%P;
ntt(p,0);
for(int i=0;i<N;i++)
p[i]=1LL*p[i]*pown(N,P-2)%P;
// for(int i=1;i<=8;i++)
// printf("%d \n",p[i]);
for(int i=1;i<N;i++)
{
if(i*2<N)
(p[i*2]+=P-1LL*c[i]*f[i]%P*f[i]%P)%=P;
// if(i<=8)
// printf("%d ",p[i]);
for(int j=0;j<g[i].size();j++)
{
int cnt=0,k=i;
while(k%g[i][j]==0)
++cnt,k/=g[i][j];
if(cnt>mx[g[i][j]])
p[i]=1LL*p[i]*pown(g[i][j],cnt-mx[g[i][j]])%P;
}
(ans+=p[i])%=P;
}
printf("%lld\n",ans*1LL*ret%P*iv2%P);
}
[THUPC 2023 初赛] 快速 LCM 变换的更多相关文章
- 为什么要进行傅立叶变换?傅立叶变换究竟有何意义?如何用Matlab实现快速傅立叶变换
写在最前面:本文是我阅读了多篇相关文章后对它们进行分析重组整合而得,绝大部分内容非我所原创.在此向多位原创作者致敬!!!一.傅立叶变换的由来关于傅立叶变换,无论是书本还是在网上可以很容易找到关于傅立叶 ...
- 离散傅立叶变换与快速傅立叶变换(DFT与FFT)
自从去年下半年接触三维重构以来,听得最多的词就是傅立叶变换,后来了解到这个变换在图像处理里面也是重点中的重点. 本身自己基于高数知识的理解是傅立叶变换是将一个函数变为一堆正余弦函数的和的变换.而图像处 ...
- 快速傅里叶变换 & 快速数论变换
快速傅里叶变换 & 快速数论变换 [update 3.29.2017] 前言 2月10日初学,记得那时好像是正月十五放假那一天 当时写了手写版的笔记 过去近50天差不多忘光了,于是复习一下,具 ...
- 快速傅立叶变换(FFT)算法
已知多项式f(x)=a0+a1x+a2x2+...+am-1xm-1, g(x)=b0+b1x+b2x2+...+bn-1xn-1.利用卷积的蛮力算法,得到h(x)=f(x)g(x),这一过程的时间复 ...
- $\mathcal{FFT}$·$\mathcal{Fast \ \ Fourier \ \ Transformation}$快速傅立叶变换
\(2019.2.18upd:\) \(LINK\) 之前写的比较适合未接触FFT的人阅读--但是有几个地方出了错,大家可以找一下233 啊-本来觉得这是个比较良心的算法没想到这么抽搐这个算法真是将一 ...
- BZOJ 2194 快速傅立叶变换之二 | FFT
BZOJ 2194 快速傅立叶变换之二 题意 给出两个长为\(n\)的数组\(a\)和\(b\),\(c_k = \sum_{i = k}^{n - 1} a[i] * b[i - k]\). 题解 ...
- 快速莫比乌斯变换(FMT)
快速莫比乌斯变换(FMT) 原文出处:虞大的博客.此仅作蒟蒻本人复习用~ 给定两个长度为n的序列 \(a_0, a_1, \cdots, a_{n-1}\)和\(b_0, b_1, \cdots, b ...
- 快速傅立叶变换(FFT)
多项式 系数表示法 设\(f(x)\)为一个\(n-1\)次多项式,则 \(f(x)=\sum\limits_{i=0}^{n-1}a_i*x_i\) 其中\(a_i\)为\(f(x)\)的系数,用这 ...
- 集合并卷积的三种求法(分治乘法,快速莫比乌斯变换(FMT),快速沃尔什变换(FWT))
也许更好的阅读体验 本文主要内容是对武汉市第二中学吕凯风同学的论文<集合幂级数的性质与应用及其快速算法>的理解 定义 集合幂级数 为了更方便的研究集合的卷积,引入集合幂级数的概念 集合幂级 ...
- 【算法】快速数论变换(NTT)初探
[简介] 快速傅里叶变换(FFT)运用了单位复根的性质减少了运算,但是每个复数系数的实部和虚部是一个余弦和正弦函数,因此系数都是浮点数,而浮点数的运算速度较慢且可能产生误差等精度问题,因此提出了以数论 ...
随机推荐
- 《数据结构-C语言》单链表
@ 目录 单链表 结构定义 初始化 建立 清空 求表长 判断是否为空表 取值 查找 插入 删除 销毁 遍历打印 测试 单链表 结构定义 #include <stdio.h> #includ ...
- 探索API接口:从概念到实践
在当今数字化时代,API(Application Programming Interface)接口成为了各种应用程序之间实现数据交互和功能集成的关键.无论是开发一个网站.构建一个移动应用还是进行数据分 ...
- HDLbits_Conwaylife
题目介绍 题目链接 Conwaylife 简介 题目要求我们实现一个康威生命游戏的电路. 该游戏在一个二维网格空间中进行,在该题目中是 16 * 16 的大小,每一个格子都有两种状态(0 或 1),代 ...
- EXE一机一码打包加密大师1.4.0更新-支持导出注册机
EXE一机一码打包加密大师可以对EXE文件进行加密处理,可以让EXE支持一机一码功能,也可以支持静态打开密码功能, 方便开发人员想用户收费. 详细软件使用说明可以查看下面的说明文档: EXE一机一码打 ...
- dedebiz 清理冗余废弃未引用图片方法
原理描述: 在原有织梦后台菜单中增加"清理冗余图片按钮",实现清理冗余图片的功能. 操作步骤: 1. 打开后台admin\sys_sql_query.php代码 在该文件中搜索如下 ...
- 解锁Java面试中的锁:深入了解不同类型的锁和它们的用途
简介 多线程编程在现代软件开发中扮演着至关重要的角色.它使我们能够有效地利用多核处理器和提高应用程序的性能.然而,多线程编程也伴随着一系列挑战,其中最重要的之一就是处理共享资源的线程安全性.在这个领域 ...
- destoon根据目录下的html文件生成地图索引
因为项目需要,destoon根据目录下的html文件生成地图索引,操作方法,代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...
- SpringBoot2.7升级到3.0的实践分享
背景 最近把项目中的技术框架做一次升级,最重要的就是SpringBoot从2.7.x升级到3.0.x,当然还会有一些周边的框架也会连带着升级,比如Mybatis Plus,SpringCloud等,话 ...
- Docker CE
3. 安装Docker CE Docker有两个分支版本:Docker CE和Docker EE,即社区版和企业版.本教程基于CentOS 7安装Docker CE. 执行如下命令,安装Docker的 ...
- MOOC慕课课表
8. 教育法学,共11单元---课件全开放状态,可以1次全学完开课时间: 2020年08月17日 ~ 2020年12月16日进行至第1周,共18周学时安排: 3-5小时每周 9. 教师职业道德与教育政 ...