0%

[QOJ9309] Graph

不是,这个能在 ACM 场上被这么多队切?

题目链接。

题意

对于一张 个点的无向图,称它是好的,当且仅当对于每一对 ,都存在路径 使得 。

称一张图是完美的,当且仅当它是好的,并且是所有 个点的好的图中,边数最小的。

给定 ,求完美的图的数量。

。

题解

考虑什么样的图是好的。

可以发现,一个图是好的当且仅当,对于每一个 ,所有 的倍数点的导出子图连通。

证明:

必要性:考虑找到最大的 使得导出子图不连通,则存在 与 不连通且 。

充分性:设 ,则在 倍数导出子图上先从 走到 再走到 即可。

再来考虑什么样的图是完美的。

假如这张图里有若干条边,对于每一条边 ,我们假设按照 从大到小顺序加边。

现在考虑, 的边已经加完了,会加入哪些 的边。

令 ,现在考虑点 导出子图内的边。

对于一个质数 ,可以发现 ,则 与 连通。

对于一个质数 ,显然 没有和其他点连边。

对于一个合数 ,可以发现 一定与其他某个点连通。

令 中质数的个数,那么这个导出子图由一个 的连通块和 个孤立点组成。

那么如果要让这个导出子图连通,至少需要连 条边。

而要让边的个数最少,一定是恰好只会连 条边,连边的方案数可以使用 prufer 序列算出来。

设 表示此时的方案数,则答案为 ,可以使用数论分块计算。

发现我们还需要求出某个区间内的质数个数,这可以用 Min_25 筛中求 的部分算出。

最终复杂度为 ,瓶颈在 Min_25 筛。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
#define debug(x) cerr<<#x<<"="<<x<<endl
using namespace std;
using ll=long long;
using ld=long double;
using pli=pair<ll,int>;
using pi=pair<int,int>;
template<typename A>
using vc=vector<A>;
inline int read()
{
int s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
inline ll lread()
{
ll s=0,w=1;char ch;
while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int mod=998244353;
int pri[400005];
bool is[400005];
ll g[2][400005];
ll f1[400005];
ll f2[400005];
ll sq,n;
inline ll qow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
inline ll& gp(ll w)
{
if(!w) return g[0][0];
if(w<=sq) return g[0][w];
return g[1][n/w];
}
inline ll get(ll m)
{
ll v=gp(m)-gp(m/2)+1;
if(m==1) return 1;
if(v==m) return qow(m%mod,m-2);
//共 v 个孤立点和 1 个连通块
return (m-v)%mod*qow(m%mod,v-1)%mod;
}
int main()
{
n=lread();
for(;(sq+1)*(sq+1)<=n;sq++);
// printf("sq=%lld\n",sq);

for(int i=2;i<=sq;i++)
{
if(!is[i]) pri[++pri[0]]=i;
for(int j=1;j<=pri[0]&&i*pri[j]<=sq;j++)
{
is[i*pri[j]]=1;
if(i%pri[j]==0) break;
}
}

vc<ll>num;
for(ll i=1;i<=sq;i++) num.push_back(i);
for(ll i=sq;i;i--) num.push_back(n/i);
num.erase(unique(num.begin(),num.end()),num.end());
for(ll i:num) gp(i)=i-1;

unsigned sw=0;
for(int i=1;i<=pri[0];i++)
{
while(sw<num.size()&&(ll)pri[i]*pri[i]>num[sw]) sw++;
for(unsigned j=num.size()-1;j>=sw;j--) gp(num[j])-=gp(num[j]/pri[i])-(i-1);
}

ll ans=1;
for(ll l=1,r;l<=n;l=r+1)
{
r=n/(n/l);
ans=ans*qow(get(n/l),r-l+1)%mod;
}
printf("%lld\n",ans);
return 0;
}