题目描述

小 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 变换的更多相关文章

  1. 为什么要进行傅立叶变换?傅立叶变换究竟有何意义?如何用Matlab实现快速傅立叶变换

    写在最前面:本文是我阅读了多篇相关文章后对它们进行分析重组整合而得,绝大部分内容非我所原创.在此向多位原创作者致敬!!!一.傅立叶变换的由来关于傅立叶变换,无论是书本还是在网上可以很容易找到关于傅立叶 ...

  2. 离散傅立叶变换与快速傅立叶变换(DFT与FFT)

    自从去年下半年接触三维重构以来,听得最多的词就是傅立叶变换,后来了解到这个变换在图像处理里面也是重点中的重点. 本身自己基于高数知识的理解是傅立叶变换是将一个函数变为一堆正余弦函数的和的变换.而图像处 ...

  3. 快速傅里叶变换 & 快速数论变换

    快速傅里叶变换 & 快速数论变换 [update 3.29.2017] 前言 2月10日初学,记得那时好像是正月十五放假那一天 当时写了手写版的笔记 过去近50天差不多忘光了,于是复习一下,具 ...

  4. 快速傅立叶变换(FFT)算法

    已知多项式f(x)=a0+a1x+a2x2+...+am-1xm-1, g(x)=b0+b1x+b2x2+...+bn-1xn-1.利用卷积的蛮力算法,得到h(x)=f(x)g(x),这一过程的时间复 ...

  5. $\mathcal{FFT}$·$\mathcal{Fast \ \ Fourier \ \ Transformation}$快速傅立叶变换

    \(2019.2.18upd:\) \(LINK\) 之前写的比较适合未接触FFT的人阅读--但是有几个地方出了错,大家可以找一下233 啊-本来觉得这是个比较良心的算法没想到这么抽搐这个算法真是将一 ...

  6. BZOJ 2194 快速傅立叶变换之二 | FFT

    BZOJ 2194 快速傅立叶变换之二 题意 给出两个长为\(n\)的数组\(a\)和\(b\),\(c_k = \sum_{i = k}^{n - 1} a[i] * b[i - k]\). 题解 ...

  7. 快速莫比乌斯变换(FMT)

    快速莫比乌斯变换(FMT) 原文出处:虞大的博客.此仅作蒟蒻本人复习用~ 给定两个长度为n的序列 \(a_0, a_1, \cdots, a_{n-1}\)和\(b_0, b_1, \cdots, b ...

  8. 快速傅立叶变换(FFT)

    多项式 系数表示法 设\(f(x)\)为一个\(n-1\)次多项式,则 \(f(x)=\sum\limits_{i=0}^{n-1}a_i*x_i\) 其中\(a_i\)为\(f(x)\)的系数,用这 ...

  9. 集合并卷积的三种求法(分治乘法,快速莫比乌斯变换(FMT),快速沃尔什变换(FWT))

    也许更好的阅读体验 本文主要内容是对武汉市第二中学吕凯风同学的论文<集合幂级数的性质与应用及其快速算法>的理解 定义 集合幂级数 为了更方便的研究集合的卷积,引入集合幂级数的概念 集合幂级 ...

  10. 【算法】快速数论变换(NTT)初探

    [简介] 快速傅里叶变换(FFT)运用了单位复根的性质减少了运算,但是每个复数系数的实部和虚部是一个余弦和正弦函数,因此系数都是浮点数,而浮点数的运算速度较慢且可能产生误差等精度问题,因此提出了以数论 ...

随机推荐

  1. 【HCDG城市行东莞站】松山湖开发者村助力企业释放数字新动能

    本文分享自华为云社区<[HCDG城市行东莞站]从"数据治理"洞察"数字化运营之道",松山湖开发者村助力企业释放数字新动能!>,作者:华为云社区精选. ...

  2. PicGo+Github图床配置

    为了将 PicGo 设置为使用 GitHub 作为图床,您需要先创建一个 GitHub 仓库用于存储图片,然后在 PicGo 中进行相应的配置.您已经创建了一个仓库,所以让我们来配置 PicGo. 安 ...

  3. 【pytorch】从零开始,利用yolov5、crnn+ctc进行车牌识别

    笔者的运行环境:python3.8+pytorch2.0.1+pycharm+kaggle用到的网络框架:yolov5.crnn+ctc项目地址:GitHub - WangPengxing/plate ...

  4. CodeForces-1324E-Sleeping-Schedule

    题意 \(Vova\)有一个睡眠时间表,一天有\(h\)小时,\(Vova\)会睡\(n\)次觉,一次睡一天,在第\(i-1\)次睡醒后,\(Vova\)在\(a_i\)或\(a_i-1\)个小时候可 ...

  5. Solution Set -「ABC 193」

    「ABC 193A」Discount Link. 略. #include<cstdio> int main() { int a,b; scanf("%d %d",&am ...

  6. How to Install Python on Linux

    Summary Hostmonster uses the preinstalled version of Python that ships with CentOS. Because of this ...

  7. CF1746F Kazaee

    prologue 数组范围一定要看好了开,不然容易我一样,调试调了一页多. 还有就是不要傻乎乎地只跑一次和哈希,因为和哈希(从下面地佬的题解中才知道)它其实算作是一种 trick(类比SA(Stimu ...

  8. vue框架,input相同标签如何定位-label定位

    一.问题提出: 后台前端框架改版,之前是angularjs,现在用vue,导致input标签定位失败,只能定位到第一个input标签,查看后台源代码发现这两个标签是一模一样,如下图: 二.问题思考过程 ...

  9. PolygonCollider2D.OverlapPoint()在小scale下失效的一种解决办法

    偶然发现PolygonCollider2D的方法OverlapPoint()有时会失效(一直返回false),测试后发现在scale(这里指世界空间的scale,后同)很小的情况下(通常在UI Can ...

  10. Radius+OpenLdap+USG防火墙认证

    1.1.安装OpenLdap # 在数据目录创建ldap文件存放ldap的配置文件 mkdir -p /data/ldap/{data,conf} docker run -p 389:389 -p 6 ...