[DTOI 2023] C. 不见故人
题目背景
虽然 luanmenglei 已经是成熟的高中生了,但每次提起 luanmenglei 八年级的女朋友时,luanmenglei 都会沉浸在美好的回忆中,不可自拔。
题目描述
给定 n , k n, k n,k 和序列 { a n } \{a_n\} {an},你同时有一个临时变量 x x x,你可以进行以下操作若干次(也可以是 0 0 0 次),一次操作的流程是:
- 选定一个区间 [ l , r ] [l,r] [l,r], ∀ i ∈ [ l , r ] \forall i\in[l,r] ∀i∈[l,r], x ← gcd ( a l , a l + 1 , ⋯ , a r ) x\leftarrow \gcd(a_l,a_{l+1},\cdots,a_r) x←gcd(al,al+1,⋯,ar)。
- ∀ i ∈ [ l , r ] \forall i\in[l,r] ∀i∈[l,r], a i ← x a_i\leftarrow x ai←x。
简而言之,你每次可以选定一个区间并将其中每个数变成这个区间的 gcd \gcd gcd。
一次操作的代价是 r − l + 1 + k r-l+1+k r−l+1+k,现在你希望把这个序列的每个数都变成相等的,求最小代价和。
如果您不了解 gcd \gcd gcd 或者多元 gcd \gcd gcd 的含义,可以参照如下定义:
- gcd ( a 1 , a 2 , … , a k ) \gcd(a_1,a_2,\dots, a_k) gcd(a1,a2,…,ak) 表示 a 1 , a 2 , … , a k a_1,a_2,\dots, a_k a1,a2,…,ak 的最大公约数,即最大的能同时整除 a 1 , a 2 , … , a k a_1,a_2,\dots, a_k a1,a2,…,ak 的正整数。
输入格式
第一行两个非负整数 n , k n,k n,k。
第二行 n n n 个数,表示 { a n } \{a_n\} {an}。
输出格式
一行一个数,表示答案。
样例 #1
样例输入 #1
10 3
2 2 2 1 2 2 2 1 2 2
样例输出 #1
13
样例 #2
样例输入 #2
10 0
2 2 2 1 2 2 2 1 2 3
样例输出 #2
9
样例 #3
样例输入 #3
11 0
2 2 2 1 2 2 2 1 1 3 3
样例输出 #3
10
提示
【样例 1 解释】
操作一次,选择区间 [ 1 , 10 ] [1,10] [1,10]。
【样例 4】
见附加文件中的 old/old4.in
与 old/old4.out
。
该样例满足测试点 9 ∼ 12 9\sim 12 9∼12 的限制。
【样例 5】
见附加文件中的 old/old5.in
与 old/old5.out
。
该样例满足测试点 13 ∼ 16 13\sim 16 13∼16 的限制。
【数据范围与提示】
对于所有数据,保证 1 ≤ n ≤ 4 × 1 0 6 1\leq n\leq 4\times 10^6 1≤n≤4×106, 0 ≤ k ≤ 1 0 9 0\leq k\leq 10^9 0≤k≤109, 1 ≤ a i ≤ 1 0 9 1\leq a_i\leq 10^9 1≤ai≤109。
每个测试点的具体限制见下表:
测试点编号 | n ≤ n\leq n≤ | k , a i ≤ k,a_i\leq k,ai≤ | 特殊性质 |
---|---|---|---|
1 1 1 | 1 0 6 10^6 106 | 1 0 9 10^9 109 | 所有数都相等 |
2 ∼ 4 2\sim 4 2∼4 | 4 4 4 | 1 0 9 10^9 109 | 无 |
5 ∼ 8 5\sim 8 5∼8 | 100 100 100 | 1 0 9 10^9 109 | 无 |
9 ∼ 12 9\sim 12 9∼12 | 1000 1000 1000 | 1 0 9 10^9 109 | 无 |
13 ∼ 16 13\sim 16 13∼16 | 1 0 6 10^6 106 | 1 0 9 10^9 109 | 无 |
17 ∼ 20 17\sim 20 17∼20 | 4 × 1 0 6 4\times 10^6 4×106 | 1 0 9 10^9 109 | 无 |
本题的读入量较大,请选择较快的读入方式,下面提供一种读入策略:
请在代码的开头加入此行:std::ios::sync_with_stdio(false);std::cin.tie(0);
。(我就不加)
请注意,加入本行后 cin/cout
的效率将大幅提高,保证其能在 250 ms
内读入所有数据,但使用后你仅能使用 cin/cout
流读入数据。
思路
- 首先,我们会发现,这道题要让所有数最终相等,那么就得让它等于 t = g c d ( a 1 , a 2 . . . a n ) t=gcd(a_1,a_2...a_n) t=gcd(a1,a2...an) 。
- 其次,我们算出了 t t t 是吧,然后我们把数列中等于 t t t 的作为区间的分界点,比如:
10 3
2 2 2 1 2 2 2 1 2 2
我们就划分成:
(2 2 2) 1 (2 2 2) 1 (2 2)
然后我们一顿区间合并(就是看如果合并的话能然答案更小,我们就要了)
- 细节1:本人发现 vector 的 size() 函数,它是 unsigned 类型的,因此如果它等于 0 再减去 1 ,会出问题,因此,这道题我们最好用数组来做。
- 细节2:开long long。
- 细节3:每个组我们要维护 l , r , w l,r,w l,r,w ,分别表示左端点,右端点,代价值。
- 细节4:因为有的区间的 g c d gcd gcd 不等于 t t t 的话,我们此时就在此区间的 w + 1 w+1 w+1 就行了。(为什么呢?因为此时我们就得扩展一下区间到那个边界,此时代价值就得+1)
- 细节5:我们合并完区间后,要把原区间的 w w w 清零。
代码
//这道题就枚举分界点呗,看看下一步是否要区间合并
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N = 5e6+10;
int w[N],a[N];
int n,m;
int cnt1,cnt2;
int ans;
int t;//因为最后的结果肯定是全为一个数
struct E{
int l,r,w;
}b[N];
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&w[i]);
t=gcd(t,w[i]);
}
for(int i=1;i<=n;i++){
if(t==w[i]){
a[++cnt1]=i;//记录分界点
}
}
//(222)1(222)1(23)
//边界特判
if(a[1]>=2)b[++cnt2]={1,a[1]-1,a[1]-1+m};
for(int i=1;i<cnt1;i++){
if(a[i+1]-a[i]==1)continue;
b[++cnt2]={a[i]+1,a[i+1]-1,a[i+1]-a[i]-1+m};
}
if(a[cnt1]<n)b[++cnt2]={a[cnt1]+1,n,n-a[cnt1]+m};
//下面记录每一段内的gcd是不是答案,如果不是,还要加上左边(或者右边)的全局gcd的一个贡献
for(int i=1;i<=cnt2;i++){
int g=w[b[i].l];
for(int j=b[i].l;j<=b[i].r;j++){
g=gcd(g,w[j]);
}
if(g!=t){
b[i].w++;
}
}
for(int i=1;i<cnt2;i++){
if(b[i+1].r-b[i].l+1+m<b[i+1].w+b[i].w){//如果合并更优,我们就合并
b[i].w=0;
b[i+1]={b[i].l,b[i+1].r,b[i+1].r-b[i].l+1+m};
}
}
for(int i=1;i<=cnt2;i++){
ans+=b[i].w;
}
printf("%lld",ans);
return 0;
}