【原文链接】
(补发)题解|2023暑期杭电多校04
1003.Simple Set Problem
思维
题目大意
给定 k k k 个非空集合,在每一个集合里选择一个数 a i a_i ai ,使得这些数之中最大值与最小值之差 d = m a x ( a i ) − m i n ( a i ) d=max(a_i)-min(a_i) d=max(ai)−min(ai) 最小,输出 d d d 的最小值
解题思路
不论哪一种选法,最小值一定是在全体元素中取到的
不妨从小到大遍历全体元素作为下界,记当前遍历元素为
a
i
a_i
ai
在每一个集合中二分查找恰好大于等于
a
i
a_i
ai 的元素选入(遍历到
a
i
a_i
ai 所在集合的时候,一定会选入
a
i
a_i
ai )
找到选定元素的最大值,做差
直到某个集合中没有大于等于
a
i
a_i
ai 的元素为止(此时该集合最大的元素选入的情况已全部考虑过),最终在得到的值中取最小即可
时间复杂度
读入:
O
(
n
)
O(n)
O(n) ;排序:
O
(
n
log
n
)
O(n\log n)
O(nlogn) ;遍历二分查找:
O
(
n
log
n
)
O(n\log n)
O(nlogn)
综合时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn)
其中
n
n
n 表示全体元素个数,
n
≤
4
e
6
n\le 4e6
n≤4e6
参考代码
void solve()
{
ll n;
cin >> n;
vector<vector<ll>> sets(n+1);
vector<ll> v;
vector<ll>::iterator it;
ll tn,t;
FORLL(i,1,n){
cin >> tn;
FORLL(j,1,tn) {cin >> t;sets[i].emplace_back(t);v.emplace_back(t);}
SORT(sets[i]);
}SORT(v);
ll re=INF,tre;
for(auto x:v){
tre=0;
FORLL(i,1,n){
it=lower_bound(ALL(sets[i]),x);
if(it==sets[i].end()) {tre=-1;break;}
tre=max(tre,*it-x);
}
if(tre==-1) break;
re=min(re,tre);
}
cout << re << endl;
}
1006.PSO
数学题、概率论
题目大意
给定一个星状图(中心节点与每个其他节点之间各有一条边,其他节点之间没有边),任选两个点,求它们之间的距离的数学期望以及最大距离的长度
解题思路
任选两个点的方案总数为
C
n
2
=
n
(
n
−
1
)
/
2
C_n^2=n(n-1)/2
Cn2=n(n−1)/2
其中一个点是中心点的方案数为
n
−
1
n-1
n−1 ,距离是
1
1
1
其余方案数为
n
(
n
−
1
)
/
2
−
(
n
−
1
)
n(n-1)/2-(n-1)
n(n−1)/2−(n−1) ,距离是
2
2
2
因此任选两个点的距离期望为:
E
=
{
[
n
(
n
−
1
)
/
2
−
(
n
−
1
)
]
×
2
+
(
n
−
1
)
×
1
}
/
C
n
2
=
[
n
(
n
−
1
)
−
(
n
−
1
)
×
2
+
(
n
−
1
)
]
/
C
n
2
=
(
n
−
1
)
2
/
C
n
2
=
2
(
n
−
1
)
/
n
\begin{align} E&=\{[n(n-1)/2-(n-1)]\times2+(n-1)\times1\}/C_n^2 \newline &=[n(n-1)-(n-1)\times2+(n-1)]/C_n^2 \newline &=(n-1)^2/C_n^2 \newline &=2(n-1)/n \end{align}
E={[n(n−1)/2−(n−1)]×2+(n−1)×1}/Cn2=[n(n−1)−(n−1)×2+(n−1)]/Cn2=(n−1)2/Cn2=2(n−1)/n
注意控制精度
参考程序
void solve()
{
ll n;
cin >> n;
printf("%.9lf",2.*(n-1)/n);
if(n>2) printf(" 2.000000000\n");
else printf(" 1.000000000\n");
}
1007.Guess
莫比乌斯函数
题目大意
给定一个正整数 n ≤ 1 e 18 n\le1e18 n≤1e18 ,求 e S ( n ) e^{S(n)} eS(n)
其中, S ( n ) = ∑ d ∣ n μ ( n d ) ln ( d ) S(n)=\sum\limits_{d|n} \mu(\dfrac{n}{d})\ln(d) S(n)=d∣n∑μ(dn)ln(d) , e e e 是自然对数 ln \ln ln 的底数, μ ( x ) \mu(x) μ(x) 是莫比乌斯函数,答案取模
前置知识点
莫比乌斯函数:
μ
(
n
)
=
{
1
,
n
=
1
;
0
,
n
含有大于
1
的完全平方因子
;
(
−
1
)
k
,
n
为
k
个质数的乘积
\mu(n)=\begin{cases} 1 &,n=1; \newline 0 &,n含有大于1的完全平方因子; \newline (-1)^k &,n为k个质数的乘积 \newline \end{cases}
μ(n)=⎩
⎨
⎧10(−1)k,n=1;,n含有大于1的完全平方因子;,n为k个质数的乘积
解题思路
对于
n
n
n 的因数
d
d
d ,根据
n
d
\dfrac{n}{d}
dn 与
d
d
d 的轮换对称性可知:
S
(
n
)
=
∑
d
∣
n
μ
(
n
d
)
ln
(
d
)
=
∑
d
∣
n
μ
(
d
)
ln
(
n
d
)
S(n)=\sum\limits_{d|n} \mu(\dfrac{n}{d})\ln(d)=\sum\limits_{d|n} \mu(d)\ln(\dfrac{n}{d})
S(n)=d∣n∑μ(dn)ln(d)=d∣n∑μ(d)ln(dn)
对 S ( n ) S(n) S(n) 做一次莫比乌斯变换(第一形式)可得: ln ( n ) = ∑ d ∣ n S ( d ) \ln(n)=\sum\limits_{d|n} S(d) ln(n)=d∣n∑S(d)
两边以 e e e 做底数得 e l n ( n ) = e ∑ d ∣ n S ( d ) ⇒ n = ∏ d ∣ n e S ( d ) e^{ln(n)}={e}^{\sum\limits_{d|n} S(d)}\Rightarrow n=\prod\limits_{d|n} e^{S(d)} eln(n)=ed∣n∑S(d)⇒n=d∣n∏eS(d)
右式是一个连乘积,其连乘长度为
n
n
n 的全体因数个数
记
F
(
x
)
=
e
S
(
x
)
F(x)=e^{S(x)}
F(x)=eS(x) ,那么答案也就是在求
F
(
n
)
F(n)
F(n)
根据上式:
- 1 = F ( 1 ) ⇒ F ( 1 ) = 1 1=F(1)\Rightarrow F(1)=1 1=F(1)⇒F(1)=1
- 对于任意质数 x x x , x = F ( 1 ) F ( x ) ⇒ F ( x ) = x x=F(1)F(x)\Rightarrow F(x)=x x=F(1)F(x)⇒F(x)=x
- 对于任意合数 x x x ,借助 1 , 2 1,2 1,2 递归计算出 F ( x ) F(x) F(x)
经过较长时间的感性理解,想到了一种遍历求 F ( x ) F(x) F(x) 方法的伪代码如下:
F[]={0}
FOR i in {1..+∞}
FOR p in Primes
if F[i*p]==0 then F[i*p]=p
(其实打表也挺好的,想个一般性的方法好难qwq)
可以得到以下
F
(
x
)
F(x)
F(x) 的正整数序列:
1
,
2
,
3
,
2
,
5
,
1
,
7
,
2
,
3
,
1
,
11
,
1
,
13
,
1
,
1
,
2
,
17
,
1
,
19
,
1
,
1
,
1
,
23
⋯
1,2,3,2,5,1,7,2,3,1,11,1,13,1,1,2,17,1,19,1,1,1,23\cdots
1,2,3,2,5,1,7,2,3,1,11,1,13,1,1,2,17,1,19,1,1,1,23⋯
这个序列的意义是 x x x 对乘积 n n n 的贡献,回到上面看那个连乘积的长度和表达形式就非常合理了
通项公式为:
F
(
n
)
=
l
c
m
{
1..
n
}
/
l
c
m
{
1..
n
−
1
}
=
{
n
,
n
=
p
i
,
p
是素数
,
i
∈
N
+
1
,
O
t
h
e
r
w
i
s
e
\begin{align} F(n)&=lcm\{1..n\}/lcm\{1..n-1\} \newline &=\begin{cases} n&,n=p^i,p是素数,i\in \N_+ \newline 1&,Otherwise \end{cases} \end{align}
F(n)=lcm{1..n}/lcm{1..n−1}={n1,n=pi,p是素数,i∈N+,Otherwise
即为最终答案
面对 n ≤ 1 e 18 n\le 1e18 n≤1e18 的庞大数据,问题就落到了如何快速求出 F ( n ) F(n) F(n) 上了。根据通项公式,只需要判断 n n n 是否有且只有一个素数非平凡因子 p p p 即可
Pollard-Rho算法是一种用于因数分解整数的快速随机算法,它可以实现在
n
1
4
n^{\frac{1}{4}}
n41 的时间下随机找到一个
n
n
n 的非平凡因子
其中用到了Miller-Rabin 素性测试算法,是一种测试素数的快速随机算法
Pollard-Rho算法的具体实现可以参考这篇博文:大数因数分解Pollard_rho 算法详解
最终根据查找结果,结合通项公式判断输出即可
参考代码
(Pollard_Rho算法及Miller_Rabin算法部分略)
ll fac=0;
bool prop(ll n){//判断n==p^i
if(n==1) return true;
if(Miller_Rabin(n)){
if(fac==0) fac=n;
else if(fac!=n) return false;
return true;
}
ll p=n;
while(p==n) p=Pollard_Rho(n);
if(prop\(p\)&&prop(n/p)) return true;
return false;
}
void solve()
{
srand(time(0));
fac=0;
ll n;cin >> n;
if(n==1) {cout << 1 << ' ';return;}
if(Miller_Rabin(n)) {cout << Get_Mod(n) << ' ';return;}
if(prop(n)) cout << Get_Mod(fac) << ' ';
else cout << 1 << ' ';
}
1010.Kong Ming Qi
猜结论题 找规律题
题目大意
如题,游戏规则和孔明棋相似
在一个
(
n
+
2
)
×
(
m
+
2
)
(n+2)\times(m+2)
(n+2)×(m+2) 大小的棋盘的中间
n
×
m
n\times m
n×m 的格子里放置着一些棋子
每次行动可以选择一个有四面相邻的棋子,使其跳过相邻的棋子,并使被跳过的棋子从棋盘上被移除
问对于给定的
n
,
m
n,m
n,m ,剩余棋子个数最少是多少
解题思路
首先考虑只有一行/一列的情况,每次只能选择倒数第二个跳出。假设
n
=
1
n=1
n=1 ,连续模拟几次可知一定剩余
⌈
m
/
2
⌉
\lceil m/2 \rceil
⌈m/2⌉ 颗棋子
对于多行多列的情况,可以考虑以下两个基础操作:
- 每次消去
1
×
3
1\times 3
1×3 区域的棋子(需要左下方有棋子,中间操作)
- 每次消去
2
×
3
2\times 3
2×3 区域的棋子(最终操作)
连续操作 1 1 1 后操作 2 2 2 ,即可在行数/列数大于 3 3 3 的情况下任意消除 3 3 3 行/列
考虑小情况 2 × 2 , 2 × 3 , 3 × 3 , 2 × 4 , 3 × 4 2\times 2,2\times 3,3\times 3,2\times 4,3\times 4 2×2,2×3,3×3,2×4,3×4 等,借助上述基础操作以及模拟可得,当 m , n m,n m,n 中有一个为3时,最少剩余 2 2 2 颗棋子,其他情况最少剩余 1 1 1 颗棋子,依此打表
参考程序
void solve()
{
ll m,n;
cin >> m >> n;
if(n>m) swap(m,n);
if(n==1) cout << (m+1)/2 << endl;
else if(!(n%3&&m%3)) cout << 2 << endl;
else cout << 1 << endl;
}
1011.Circuit
图论-最短路-Floyd
题目大意
给定一个无重边、无自循环的带权有向图
G
=
(
V
,
E
)
,
∣
V
∣
=
n
,
∣
E
∣
=
m
G=(V,E),|V|=n,|E|=m
G=(V,E),∣V∣=n,∣E∣=m
求
G
G
G 的所有回路中最短回路的长度以及个数,个数取模
解题思路
全图范围的最短路计数,考虑使用弗洛伊德算法//
为了不重不漏的计数,考虑每个最短回路仅在其最大标号处计数
对于一条有向回路,可以将其最大编号到下一点的有向边单独分割出来,剩余部分即为从起点到回路上最大编号点的最短路//
借助Floyd算法,在 O ( n 3 ) O(n^3) O(n3) 的时间内,计算最短路的同时,可以进行最短路方案数的计数,这里详细阐述一下计数过程
原图 G G G 存在有向边 u → v u\rightarrow v u→v ,则 u → v u\rightarrow v u→v 的初始方案数置为 1 1 1 ,没有边则置为 0 0 0
加入点
k
k
k 时,过点
k
k
k 的
i
→
j
i\rightarrow j
i→j 的最短路的方案数为:
i
→
k
i\rightarrow k
i→k 的最短方案数
×
\times
×
k
→
j
k\rightarrow j
k→j 的最短路方案数
如果发生最短路更新,则方案数一并更新//另判断如果长度一致,则方案数需相加
每次完成更新后,假设 k k k 已经是以 i i i 为起点的最短回路上的最大编号点了,此时原图 G G G 中如果有 k → i k\rightarrow i k→i 的有向边,则可以构成回路,比较计数即可//
更加具体的内容可以参考代码注释
温馨提示: I N F INF INF 的值需要设置到 1 e 18 1e18 1e18 ,因为两段方案数乘积最大可以为 M O D 2 ≈ 1 e 18 MOD^2 \approx 1e18 MOD2≈1e18 ,补题时因为 I N F INF INF 的值不够大卡了两个小时左右
时间复杂度
Floyd算法: O ( n 3 ) O(n^3) O(n3)
参考代码
const long long INF=1e18;
#define N 505
ll dist[N][N]={0},cnt[N][N]={0},ori[N][N]={0};
ll mindist=INF,cntmin=0;
void RESET_G(ll n){
FORLL(i,1,n)
FORLL(j,1,n){
ori[i][j]=0;
dist[i][j]=(i==j?0:INF);
cnt[i][j]=0;
}
mindist=INF,cntmin=0;
}//重置
void Floyd(ll n){
FORLL(k,1,n)
FORLL(i,1,n){
FORLL(j,1,n){//Floyd
if(dist[i][j]>dist[i][k]+dist[k][j]){
dist[i][j]=dist[i][k]+dist[k][j];
cnt[i][j]=mul(cnt[i][k],cnt[k][j]);//cnt[i][j]=cnt[i][k]*cnt[k][j]
//方案数为两段方案数的乘积,更新同步
}else if(dist[i][j]==dist[i][k]+dist[k][j]){
addto(cnt[i][j],mul(cnt[i][k],cnt[k][j]));//cnt[i][j]+=cnt[i][k]*cnt[k][j]
//相等则方案数相加
}
}
if(i<k&&ori[k][i]){//假设到k为以i为起点的单向环上最大点
if(ori[k][i]+dist[i][k]<mindist) {mindist=ori[k][i]+dist[i][k];cntmin=cnt[i][k];}
else if(ori[k][i]+dist[i][k]==mindist) addto(cntmin,cnt[i][k]);//cntmin+=cnt[i][k]
}
}
}
void solve()
{
ll n,m;
cin >> n >> m;
RESET_G(n);
ll u,v,w;
FORLL(i,1,m){
cin >> u >> v >> w;
ori[u][v]=w;
dist[u][v]=w;
cnt[u][v]=1;
}
Floyd(n);
if(cntmin) cout << mindist << ' ' << cntmin << endl;
else cout << "-1 -1" << endl;
}
1012.a-b Problem
贪心
题目大意
给定一堆
n
n
n 个石子,每个石子有非负值
a
i
,
b
i
a_i,b_i
ai,bi ,分别代表Alice和Bob取该石子可以得到的分数
两人都希望自己的得分最大化,Alice执先,求最终Alice和Bob的分数差
解题思路
取某石子的价值不仅在于自身得分,也在于让对手失去他那部分的分值。因此根据每颗石子的分数之和排序,从大到小依次取即可
参考代码
void solve()
{
ll n;
cin >> n;
vector<pair<ll,pll>> v;
pair<ll,pll> t;
FORLL(i,1,n){
cin >> t.second.first >> t.second.second;
t.first=t.second.first+t.second.second;
v.emplace_back(t);
}
SORT(v);
ll A,B;A=B=0;
for(ll i=n-1;i>=0;i--){
if((n-1-i)&1) B+=v[i].second.second;
else A+=v[i].second.first;
}
ll re=A-B;
cout << re << endl;
}