由题意,
p
p
p 需要满足:当
a
i
≠
1
a_i\ne1
ai=1 时,
m
i
a
i
≤
p
≤
m
i
(
a
i
−
1
)
\frac{m}{ia_i}\leq p\leq \frac{m}{i(a_i-1)}
iaim≤p≤i(ai−1)m;当
a
i
=
1
a_i=1
ai=1 时,
m
i
a
i
≤
p
\frac{m}{ia_i}\leq p
iaim≤p。所有的不等式做交集得到满足要求的
p
p
p。
担心精度损失问题。注意到
p
p
p 一定是整数。故
p
<
2.1
p<2.1
p<2.1 与
p
<
3
p<3
p<3 是等价的;
p
≥
2.1
p\geq 2.1
p≥2.1 与
p
≥
3
p\geq 3
p≥3 是等价的。故不需要使用浮点数,判断分子是否整除分母,然后分类处理即可。
时间复杂度为
O
(
n
)
\mathcal O(n)
O(n)。
代码
#include<bits/stdc++.h>usingnamespace std;constint maxn =1e5+100;constint INF =2147483647;int n, m, a[maxn];int ansi =-1, ansii =2147483647;intmain(){scanf("%d %d",&n,&m);for(int i =1; i <= n; i++){int a;scanf("%d",&a);if(m %(i * a)==0) ansi =max(ansi, m /(i * a));else ansi =max(ansi, m /(i * a)+1);if(a !=1){if(m %(i *(a -1))==0) ansii =min(ansii, m /(i *(a -1)));else ansii =min(ansii, m /(i *(a -1))+1);}}if(ansii <= ansi)printf("0"),exit(0);if(ansi ==-1|| ansii == INF)printf("xiaogougege"),exit(0);printf("%d", ansii - ansi);return0;}
set 的定义:set 是一个内部自动有序且不含重复元素的容器,其实现自动去重并升序排序。使用 set 需要用到头文件:#include< set >。可以用迭代器 *it 去访问 set 中的元素。
常用函数及其功能
由于 set 内部的自动升序,因此插入、删除、查找为某个值的元素的时间复杂度均为
O
(
log
n
)
\mathcal O(\log n)
O(logn)。
思路
考虑如何判断元素是否在序列中。用
v
i
s
i
vis_i
visi 记录
i
i
i 在目前的序列中出现的次数。若
v
i
s
i
=
0
vis_i=0
visi=0,则说明
i
i
i 不在序列中。
考虑构造
∑
i
=
1
n
∣
a
i
+
1
−
a
i
∣
\sum\limits_{i=1}^n|a_{i+1}-a_i|
i=1∑n∣ai+1−ai∣ 的最小值。首先我们对
a
a
a 进行不降序排序,得到
a
1
a
2
…
a
n
a_1a_2\dots a_n
a1a2…an。由几何直观容易证明,此序列使得
∑
i
=
1
n
∣
a
i
+
1
−
a
i
∣
\sum\limits_{i=1}^n|a_{i+1}-a_i|
i=1∑n∣ai+1−ai∣ 最小,且最后的答案为
2
(
max
{
a
i
}
−
min
{
a
i
}
)
2(\max\{a_i\}-\min\{a_i\})
2(max{ai}−min{ai})。
考虑如何得到最大值与最小值。set 可以实现
O
(
log
n
)
\mathcal O(\log n)
O(logn) 地插入元素与删除元素,
O
(
1
)
\mathcal O(1)
O(1) 地查询最大值与最小值。
时间复杂度为
O
(
(
n
+
q
)
log
n
)
\mathcal O((n+q)\log n)
O((n+q)logn)。
代码
#include<bits/stdc++.h>usingnamespace std;constint maxn =1e6+100;int n, q, vis[maxn];
set <int> s;intmain(){scanf("%d %d",&n,&q);for(int i =1; i <= n; i++){int a;scanf("%d",&a);
vis[a]++, s.insert(a);}while(q --){int op, x;scanf("%d %d",&op,&x);if(op ==1){if(!vis[x]){printf("-1\n");continue;}if(!-- vis[x])
s.erase(x);printf("%d\n",2*(*--s.end()-*s.begin()));}else{if(!vis[x]++)
s.insert(x);printf("%d\n",2*(*--s.end()-*s.begin()));}}return0;}
思考最后相同的数是什么。首先,对于一个区间我们可以划分为若干个子区间,对子区间在进行上述操作,如此继续,则一定存在某个时刻,某个区间分为若干个后不可再分,这时我们按题目中的要求合并,最后得到这个相同的数一定是
gcd
{
a
1
,
a
2
,
…
a
n
}
\gcd\{a_1,a_2,\dots a_n\}
gcd{a1,a2,…an},记为
G
G
G。
思考如何得到最小代价。容易想到最初值为
G
G
G 的元素,在不影响答案的前提下,应当尽可能少地参与到操作中。于是,我们将最初值为
G
G
G 的元素标记为红色,形成若干红色区间。则记被红色区间分隔开的若干个区间为黑色区间,并记
p
p
p 为黑色区间的个数,
l
i
,
r
i
l_i,r_i
li,ri 分别为第
i
i
i 个黑色区间的左端点与右端点。则接下来我们只需要通过讨论这些黑色区间得到最小代价。
思考如何通过黑色区间得到最小代价。当
p
=
0
p=0
p=0 时,答案显然为
0
0
0。下面讨论
p
>
0
p>0
p>0 的情形。易得,
r
i
r_i
ri 为有效讨论点,因为若把
1
1
1 到
j
,
j
∈
[
l
i
,
r
i
)
j,j\in[l_i,r_i)
j,j∈[li,ri) 变为
G
G
G,再把
j
+
1
j+1
j+1 到
r
i
r_i
ri 变为
G
G
G,相比于直接把
1
1
1 到
r
i
r_i
ri 变成
G
G
G 会多消耗一次
k
k
k。故我们令
d
p
i
dp_i
dpi 表示把
1
1
1 到
r
i
r_i
ri 的全部元素变为
G
G
G 付出的最小代价,则
d
p
p
dp_p
dpp 即为最后的答案。
思考
d
p
i
dp_i
dpi 的转移方程。若
d
p
i
dp_i
dpi 的转移没有跨过红色区间,则
d
p
i
←
d
p
i
−
1
+
r
i
−
l
i
+
1
+
k
+
[
gcd
{
a
j
∣
j
∈
[
l
i
,
r
i
]
}
≠
G
]
dp_i\gets dp_{i-1}+r_i-l_i+1+k+[\gcd\{a_j|j\in[l_i,r_i]\}\ne G]
dpi←dpi−1+ri−li+1+k+[gcd{aj∣j∈[li,ri]}=G]。若
d
p
i
dp_i
dpi 的转移跨过了红色区间,则
d
p
i
←
min
{
d
p
j
+
r
i
−
l
j
+
1
+
1
+
k
∣
j
∈
[
0
,
i
−
1
)
}
dp_i\gets \min\{dp_j+r_i-l_{j+1}+1+k|j\in[0,i-1)\}
dpi←min{dpj+ri−lj+1+1+k∣j∈[0,i−1)}。故我们只需要动态维护
d
p
j
−
l
j
+
1
,
j
∈
[
0
,
i
−
1
)
dp_j-l_{j+1},j\in[0,i-1)
dpj−lj+1,j∈[0,i−1) 的最小值即可,用 set 可以很优秀的实现。
最坏时间复杂度为
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn)。
代码
#include<bits/stdc++.h>usingnamespace std;constint maxn =4e6+100;int n, k, a[maxn], l[maxn], r[maxn], p =0, G, dp[maxn], last_dp;intgcd(int a,int b){if(a < b)swap(a, b);if(b ==0)return a;elsereturngcd(b, a % b);}
set <int> s;intmain(){scanf("%d %d",&n,&k);for(int i =1; i <= n; i++){scanf("%d",&a[i]);
G =gcd(G, a[i]);}int ii =1;// [l_i,r_i] 的获得有细节,需要注意。while(ii <= n){while(a[ii]== G && ii <= n) ii ++;
l[++ p]= ii;// l[p] == n + 1 表明最后这一块全为 Gif(l[p]== n +1){
p --;break;}while(a[ii]!= G && ii <= n) ii ++;
r[p]= ii -1;}if(p ==0)printf("0"),exit(0);// 为了保证正确性,定义 dp_0 - l_1 存在。
last_dp =-l[1];for(int i =1; i <= p; i++){int Gi =0;for(int j = l[i]; j <= r[i]; j++)
Gi =gcd(Gi, a[j]);
dp[i]= dp[i -1]+ r[i]- l[i]+1+ k +(Gi == G ?0:1);if(!s.empty()) dp[i]=min(dp[i], r[i]+1+ k +*s.begin());
s.insert(last_dp);
last_dp = dp[i]- l[i +1];}printf("%d", dp[p]);return0;}