[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)运用了单位复根的性质减少了运算,但是每个复数系数的实部和虚部是一个余弦和正弦函数,因此系数都是浮点数,而浮点数的运算速度较慢且可能产生误差等精度问题,因此提出了以数论 ...
随机推荐
- Programming abstractions in C阅读笔记:p123-p126
<Programming Abstractions In C>学习第50天,p123-p126,总结如下: 一.技术总结 1.notaion 这也是一个在计算机相关书籍中出现的词,但有时却 ...
- Flutter系列文章-Flutter在实际业务中的应用
不同场景下的解决方案 1. 跨平台开发: 在移动应用开发中,面对不同的平台(iOS和Android),我们通常需要编写两套不同的代码.而Flutter通过一套代码可以构建适用于多个平台的应用,大大提高 ...
- 如何利用电商API接口来获取商品数据
要利用电商API接口来获取商品数据,我们可以按照以下步骤实现: 确定电商平台和API接口 不同的电商平台提供不同的API接口,因此我们需要确定我们要获取商品数据的电商平台,并选择相应的API接口进行调 ...
- js获取当前月的天数
//取得本月天数(实际代码:) var now=new Date(); var d = new Date(now.getFullYear(),now.getMonth()+1,0); var days ...
- Vue源码学习(二):<templete>渲染第一步,模板解析
好家伙, 1.<template>去哪了 在正式内容之前,我们来思考一个问题, 当我们使用vue开发页面时,<tamplete>中的内容是如何变成我们网页中的内容的? 它会经历 ...
- Go语句与表达式深度解析:全案例手册
关注公众号[TechLeadCloud],分享互联网架构.云服务技术的全维度知识.作者拥有10+年互联网服务架构.AI产品研发经验.团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资 ...
- @RequiredArgsConstructor和@Authwired
我们在java后端书写接口时,对service层成员变量的注入和使用有以下两种实现方式: 1) @RequiredArgsConstructor import lombok.RequiredArgsC ...
- 噢耶!字节后端Offer,拿到了
很多同学反馈多搞点面经,说来就来! 今天分享一位拿到字节跳动实习Offer的面经,没错,Java转Go. 别问我选Java还是选Go,成年人不做选择题.先搞定一个语言,再学第二语言从来不是难事. 无论 ...
- rte-rtc
活动内容 个人中心 立即报名 活动详情 RTE大会(原"RTC大会")创立于2015年,是亚太首个.迄今为止规模最大的实时互联网技术盛会,覆盖200+行业场景 ...
- 教育法学第九章单元测试MOOC
第九章单元测试 返回 本次得分为:100.00/100.00, 本次测试的提交时间为:2020-09-06, 如果你认为本次测试成绩不理想,你可以选择 再做一次 . 1 单选(5分) 作为教师最基本的 ...