零.送给未来复习的自己:

如果一段时间后看不懂自己的题解了,可以尝试拿\(n=12\)这一样例模拟,因为\(n=2^2\times 3^1\)只有两个不同的质因子,并且一个是指数只有\(2\),一个指数只有\(1\),所以可以画一个大小为\((2+1)\times(1+1)\)的二维表格来辅助理解容斥部分

一.题意:

对于给定的\(n\),求有多少集合\(\{1,2,3...,n\}\)的子集满足最大公约数\((gcd)\)为\(1\),而最小公倍数\((lcm)\)为\(n\)。答案对\(998244353\)

取模。

二.思路:

首先看到\(n\leq 10^{18}\)这一逆天范围,又结合题目中的数论元素,我们考虑对\(n\)质因数分解:

\[n=\prod_{i=1}^{r}p_i^{a_i}
\]

现在转化题目,考虑什么时候满足题意,通过gcd和lcm的形式化定义我们发现,\(n\)的每一个指数\(a_i\)都至少在这个子集中出现一次,换句话说也就是,对于每一个\(i\),子集中至少存在一个数包含指数\(a_i\)。同理,对于每一个\(i\),子集中一定至少包含一个数使得\(p_i\)的指数为\(0\)

现在我们考虑\(n\)只包含\(1\)个质因子的特殊情况,你发现我们如果考虑总情况数,他就包含\(2^{a+1}\)种方案,而我们应当减去不包含\(0\)和不包含\(a\)的方案,方案数分别都是是\(2^a\),而根据容斥原理,我们应当把两个都不包含的方案加回来,即:\(2^{a-1}\)。

我们思考,上述过程是否可以拓展呢?

显然是可以的,我们对于每一个\(i\in[1,r]\)都去考虑上述四种情况,枚举每个质因数每次是哪种选取方式,再套用容斥原理即可,发现上面有四种方案,所以复杂度大概是在 \(O(\omega(n)\cdot 4^{\omega(n)})\) 的,(\(\omega(n)\)指的是\(n\)中包含的不同的质因子数量),可以通过前\(60\)分的测试点。

考虑优化,我们发现不包含\(0\)和不包含\(a_i\)其实方案数是一样的且对于每一次计算都是对称出现的,所以我们可以把他们考虑成一种情况,最终答案乘以二就好了,这样就优化成了 \(O(\omega(n)\cdot 3^{\omega(n)})\) 的复杂度,那这样你就可以拿到\(80\)分。具体怎么在实现容斥的过程中体现某个质因子的选取状态呢?其实我们可以类比二进制状态压缩的思想生成一个三进制数去存储当前的状态,\(0\)表示这一个质因子没有限制,\(1\)表示我们不选\(0\)或者不选\(a_{i}\)的方案数,\(2\)表示我们两个都不选。

这样你会发现非常方便,我们对于每一个质因子,他的选取方案就是:

\[2^{a_i+1-numbit_i}
\]

其中\(numbit_i\)表示这一位的三进制数是多少,所以对于最后所有的质因子我们就可以列出方案:

\[2^{\prod_{i=1}^r(a_i+1-numbit_i)+cnt_1}
\]

其中\(cnt_1\)表示在三进制状态集合下总共有多少质因子状态为\(1\),最后输出方案即可。

code for 80pts:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define int long long
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch > '9' || ch < '0'){if(ch == '-'){f = -1;}ch = getchar();}
while(ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}
return x * f;
} const int MOD = 998244353;
const int PRIME_NUM = 20;
int n,a[PRIME_NUM],cnt,three_shift[PRIME_NUM],ans,bit_3; void divide(int x){
int sq = std::sqrt(x);
for(int i = 2;i <= sq;++i)
{
if(x % i == 0)
{
int res = 0;
while(x % i == 0) x /= i,++res;
a[++cnt] = res;
}
}
if(x == 1) return ;
a[++cnt] = 1;
} int qpow(int a,int b)
{
int res = 1;
while(b)
{
if(b & 1) res = (res * a) % MOD;
a = (a * a) % MOD;
b >>= 1;
}
return res;
} void change(int x)
{
for(int i = 1;i <= bit_3;++i) three_shift[i] = 0;
bit_3 = 0;
while(x)
{
three_shift[++bit_3] = x % 3;
x /= 3;
}
} signed main()
{
n = read();
divide(n); int up_limit = qpow(3,cnt);
for(int i = 0;i < up_limit;++i)
{
change(i);
int res = 1,cnt_parity = 0,cnt_1 = 0;//cnt_parity判断最终容斥系数奇偶
for(int j = 1;j <= cnt;++j)
{
res = res * (a[j] - three_shift[j] + 1) % MOD;
if(three_shift[j] == 1) ++cnt_1;
cnt_parity += three_shift[j];
}
int res1 = qpow(2,res + cnt_1);
if(cnt_parity % 2) ans = (ans - res1 + MOD) % MOD;
else ans = (ans + res1) % MOD;
}
std::cout << ans;
return 0;
}

所以问题来了,为什么只有\(80\)分呢,看着上面的复杂度很可以过啊。其实真正的瓶颈在于找质因子这一部分,他的复杂度是根号级别的,而\(n\)的上界达到了\(10^{18}\)这显然是无法接受的,所以需要新的,更快的算法工具辅助我们获取质因子。这里考虑使用miller-rabin算法,你可以永远相信随机数的速度。

tips:在最终算答案的时候,我们最好使用欧拉定理来降幂,否则时间可能会超过\(2s\),(反正我是第一次没用没有过最后两个点)。

code for 100pts

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define int long long
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch > '9' || ch < '0'){if(ch == '-'){f = -1;}ch = getchar();}
while(ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}
return x * f;
} const int MOD = 998244353;
const int PRIME_NUM = 20;
int n,a[PRIME_NUM],cnt,ans,Cnt,pri[1000010]; int qpow(__int128 a,int b,int P)
{
int res = 1;
while(b)
{
if(b & 1) res = (res * a) % P;
a = (a * a) % P;
b >>= 1;
}
return res;
} bool check(int a,int b)
{
int tmp = b - 1;
if(qpow(a,tmp,b) != 1) return 0;
while(tmp % 2 == 0)
{
tmp >>= 1;
if(qpow(a,tmp,b) == b - 1) return true;
if(qpow(a,tmp,b) != 1) return false;
}
return true;
} bool check_prime(int x)
{
for(int i = 1;i <= 12;++i)
{
if(!check(pri[i],x)) return false;
}
return true;
} bool vis[1000010];
void divide(int x)
{
for(int i = 2;i <= 1e6;++i)
{
if(!vis[i]) pri[++Cnt] = i;
for(int j = 1;j <= Cnt && pri[j] * i <= 1e6;++j)
{
vis[i * pri[j]] = true;
if(i % pri[j] == 0) break;
}
} for(int i = 1;i <= Cnt;++i)
{
if(x % pri[i] == 0)
{
++cnt;
while(x % pri[i] == 0) x /= pri[i],++a[cnt];
}
}
if(x > 1)
{
int t = (int)sqrt(x);
if(t * t == x)
{
a[++cnt] = 2;
}
else
{
a[++cnt] = 1;
if(!check_prime(x)) a[++cnt] = 1;
}
}
} signed main()
{
n = read();
divide(n); int up_limit = qpow(3,cnt,MOD);
for(int i = 0;i < up_limit;++i)
{
int res = 1,cnt_parity = 0,cnt_1 = 0,tmp = i;//cnt_parity判断最终容斥系数奇偶
for(int j = 1;j <= cnt;++j)
{
int now = tmp % 3;
if(now == 1) ++cnt_1;
cnt_parity += now;
res = res * (a[j] + 1 - now);
tmp /= 3;
}
int res1 = qpow(2,(res + cnt_1) % (MOD - 1),MOD);//数论欧拉定理
if(cnt_parity % 2) ans = (ans - res1 + MOD) % MOD;
else ans = (ans + res1) % MOD;
}
std::cout << ans;
return 0;
}

2.20模拟赛T3解析的更多相关文章

  1. 体育成绩统计——20180801模拟赛T3

    体育成绩统计 / Score 题目描述 正所谓“无体育,不清华”.为了更好地督促同学们进行体育锻炼,更加科学地对同学们进行评价,五道口体校的老师们在体育成绩的考核上可谓是煞费苦心.然而每到学期期末时, ...

  2. 20180520模拟赛T3——chess

    [问题描述] 小美很喜欢下象棋. 而且她特别喜欢象棋中的马. 她觉得马的跳跃方式很独特.(以日字格的方式跳跃) 小芳给了小美一张很大的棋盘,这个棋盘是一个无穷的笛卡尔坐标. 一开始\(time=0\) ...

  3. 【2019.8.20 NOIP模拟赛 T3】小X的图(history)(可持久化并查集)

    可持久化并查集 显然是可持久化并查集裸题吧... 就是题面长得有点恶心,被闪指导狂喷. 对于\(K\)操作,直接\(O(1)\)赋值修改. 对于\(R\)操作,并查集上直接连边. 对于\(T\)操作, ...

  4. 小X归来 模拟赛1 解析

    Problem1 单峰 小X 归来后,首先对数列很感兴趣.他想起有1类特殊的数列叫单峰数列. 我们说一个数列 {ai} 是单峰的,当且仅当存在一个位置 k 使得 ai < ai+1(i < ...

  5. 20180711模拟赛T3——聚变

    文件名: fusion 题目类型: 传统题 时间限制: 3秒 内存限制: 256MB 编译优化: 无 题目描述 知名科学家小A在2118年在计算机上实现了模拟聚变的过程. 我们将她研究的过程简化. 核 ...

  6. NOIP欢乐模拟赛 T3 解题报告

    3.小澳的葫芦 (calabash.cpp/c/pas) [题目描述] 小澳最喜欢的歌曲就是<葫芦娃>. 一日表演唱歌,他尽了洪荒之力,唱响心中圣歌. 随之,小澳进入了葫芦世界. 葫芦世界 ...

  7. 20161005 NOIP 模拟赛 T3 解题报告

    subset 3.1 题目描述 一开始你有一个空集,集合可以出现重复元素,然后有 Q 个操作 1. add s 在集合中加入数字 s. 2. del s 在集合中删除数字 s.保证 s 存在 3. c ...

  8. 神奇的NOIP模拟赛 T3 LGTB 玩THD

    LGTB 玩THD LGTB 最近在玩一个类似DOTA 的游戏名叫THD有一天他在守一座塔,对面的N 个小兵排成一列从近到远站在塔前面每个小兵有一定的血量hi,杀死后有一定的金钱gi每一秒,他都可以攻 ...

  9. 2017-10-6模拟赛T3 丝(filament)

    题目 题解 20分实在想不到是什么做法…… 40分做法,从小到大枚举最小循环节长度,O(n) check即可,总复杂度O(n^2). 100分做法: 看到数据范围,T*n<=10^6,可知这题需 ...

  10. ztz11的noip模拟赛T3:评分系统

    代码: #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> ...

随机推荐

  1. 再论“ArcGIS AddIN之工具不可用”

    工作需要,开发arcgis的addin插件.从网上找了工程范例,编译dll,没有生成esriAddIn文件. 第二次,重新创建addin类型的动态库工程,从范例中复制类文件到工程中,编译dll,正常生 ...

  2. JavaScript入门笔记day2

    文章目录 常用互动方法 1. document.write() 直接向页面输出内容 2. `alert();`弹出消息对话框 3. confirm消息对话框 4. prompt弹出消息对话框,用于需要 ...

  3. Seata源码—9.Seata XA模式的事务处理

    大纲 1.Seata XA分布式事务案例及AT与XA的区别 2.Seata XA分布式事务案例的各模块运行流程 3.Seata使用Spring Boot自动装配简化复杂配置 4.全局事务注解扫描组件的 ...

  4. gcc、g++命令

    gcc 与 g++ 分别是 gnu 的 c & c++ 编译器 gcc/g++ 在执行编译工作的时候,总共需要4步: 1.预处理,生成 .i 的文件[预处理器cpp] 2.将预处理后的文件转换 ...

  5. java springboot图片上传和访问

    上传 @RequestMapping("/uploadImg") public Result uploadImg(HttpServletRequest request, Multi ...

  6. Kubernetes中Service学习笔记

    我们知道 Pod 的生命周期是有限的.可以用 ReplicaSet 和Deployment 来动态的创建和销毁 Pod,每个 Pod 都有自己的 IP 地址,但是如果 Pod 重建了的话那么他的 IP ...

  7. Python基础—初识函数(二)

    1.给函数参数增加元信息 写好一个函数,然后想为这个函数的参数增加一些额外的信息,这样的话其他使用者就能清楚的知道这个函数应该怎么使用. 使用函数参数注解是一个很好的办法,它能提示程序员应该怎样正确使 ...

  8. kubernetes获取Pod内容器信息

    一.简单说明 在实际的业务需求中,我们可能需要在写yaml文件的时候,可以在Pod的container内获取Pod的spec,metadata等信息,包含:node的名称,pod的名称,pod的nam ...

  9. [书籍精读]《你不知道的JavaScript(下卷)》精读笔记分享

    写在前面 书籍介绍:JavaScript这门语言简单易用,很容易上手,但其语言机制复杂微妙,即使是经验丰富的JavaScript开发人员,如果没有认真学习的话也无法真正理解.本套书直面当前JavaSc ...

  10. HyperWorks批处理网格的类型设置

    网格类型设置(Configuration Tab) HyperWorks中BatchMesher 的 Configuration Tab 向用户提供了网格方案类型(Mesh Type)的选择.一个典型 ...