【题解】[ABC306G] Return to 1(数论)
【题解】[ABC306G] Return to 1
题目链接
题意概述
本题多测,
对于每组数据,给定一个
问从顶点
数据范围
思路分析
首先,可以删掉图中无法从顶点
设
证明:
由于我们要从开始恰好走
步回到 。假设原图中有两个长度为 和 的环,那么就相当于找两个非负整数 和 ,使得 ,其中 。 那么问题就转化为:对于方程
,它有解的充要条件当且仅当 ,其中 。 充分性:根据裴蜀定理可知:存在
使得 ,又 ,所以 ,所以方程有整数解 ; 必要性:因为
, 是 的最大公约数,所以 ,所以 ,即 。 更一般地,当原图中环的个数超过两个时,我们可以用归纳法证明出对于
里所有元素 ,方程 有解当且仅当 。
那么我们现在就可以有一个清晰的思路:暴力把所有的环长求出来,取它们的
但是当经过
走到这一步,问题实际上可以转化为:
给定若干个经过
的环,如何求出这些环长的 ?
我们考虑反向思考,首先要弄明白一个问题:对于一个数
有一个方法是:从
证明:
首先考虑简单环:
- 如果给定一堆简单环,我们从
开始 dfs 给每个点标记一个模 意义下的 ,那么每个环一定都是按照一定顺序像 这样标号的。 - 假设一个环环长为
,那么从 开始 dfs,到第 个点的过程中,对于非第 个点,无论如何一定有 ,这是显然的。 - 对于第
个点当且仅当它的 不是 时,这个点的编号加一模 不等于 ,此时 一定就不是该环环长的约数,与“ 是所有环长公约数”矛盾。 然后考虑复杂环:
复杂环与简单环的最主要区别就在于简单环一定不会存在公用边,也就是不会多个环经过同一条边的情况,而复杂环可能会出现。
假设有两个环,环长均是
的约数,从 出发经过不同的路径到达一个点 ,然后再沿着同一条从 到 路径回到 。比如下图(两个环分别从 出发沿着不同路径到达 再沿着相同的路径从 回到 )。定义这个 为两个环的交汇点, 和 为两个环环长。 那么对于两个环非交汇点,同样无论如何一定有
(与简单环相同)。 对于两个环交汇点
(相当于图中的 ),如果存在其中一个环的一条边 ,使得模 意义下不满足 ,那么当且仅当这两个环的环长差 不是 的倍数。 由于
是 和 的公约数,所以 。根据更相减损法,有 ,所以 ,所以 一定是 的倍数。
由于
同理,要使得
那么我们直接枚举所有边
最后一步就是要判断这个
由于 Yes
,反之是 No
。
总体来说,这个题大的方面分为三步:先删掉图中无关的点;再将问题转化为求所有环长的
总复杂度
注意多测清空。
代码实现
//G
//The Way to The Terminal Station…
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#define mk make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=2e5+10;
int ok[maxn],dep[maxn];//ok[i] 表示 i 是否为有用点(即能不能到达 1)。
basic_string<int>edge[maxn],edge2[maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void dfs(int x)
{
ok[x]=1;
for(int y:edge2[x])
{
if(ok[y])continue;
dfs(y);
}
}
void dfs2(int x)
{
for(int y:edge[x])
{
if(!ok[y])continue;
if(dep[y]!=-1)continue;
dep[y]=dep[x]+1;
dfs2(y);
}
}
int gcd(int a,int b){if(b==0)return a;return gcd(b,a%b);}
int main()
{
int T=read();
while(T--)
{
int n,m;
n=read();m=read();
for(int i=1;i<=n;i++)edge[i].clear(),edge2[i].clear(),dep[i]=-1,ok[i]=0;
for(int i=1;i<=m;i++)
{
int u,v;
u=read();v=read();
edge[u]+=v;
edge2[v]+=u;
}
dfs(1);
dep[1]=0;
dfs2(1);
int d=0;
for(int u=1;u<=n;u++)
{
if(dep[u]==-1)continue;
for(int v:edge[u])
{
if(dep[v]==-1)continue;
d=gcd(d,abs(dep[u]+1-dep[v]));
}
}
if(!d){cout<<"No"<<'\n';continue;}//没有经过 1 的环。
while(d%2==0)d/=2;
while(d%5==0)d/=5;
if(d==1)cout<<"Yes"<<'\n';
else cout<<"No"<<"\n";
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2022-06-18 CF 绿蓝题做题记录