[DTOI 2023] C. 不见故人(贪心)

108 篇文章 0 订阅

[DTOI 2023] C. 不见故人

题目背景

虽然 luanmenglei 已经是成熟的高中生了,但每次提起 luanmenglei 八年级的女朋友时,luanmenglei 都会沉浸在美好的回忆中,不可自拔。

题目描述

给定 n , k n, k n,k 和序列 { a n } \{a_n\} {an},你同时有一个临时变量 x x x,你可以进行以下操作若干次(也可以是 0 0 0 次),一次操作的流程是:

  1. 选定一个区间 [ 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) xgcd(al,al+1,,ar)
  2. ∀ i ∈ [ l , r ] \forall i\in[l,r] i[l,r] a i ← x a_i\leftarrow x aix

简而言之,你每次可以选定一个区间并将其中每个数变成这个区间的 gcd ⁡ \gcd gcd

一次操作的代价是 r − l + 1 + k r-l+1+k rl+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.inold/old4.out

该样例满足测试点 9 ∼ 12 9\sim 12 912 的限制。

【样例 5】

见附加文件中的 old/old5.inold/old5.out

该样例满足测试点 13 ∼ 16 13\sim 16 1316 的限制。

【数据范围与提示】

对于所有数据,保证 1 ≤ n ≤ 4 × 1 0 6 1\leq n\leq 4\times 10^6 1n4×106 0 ≤ k ≤ 1 0 9 0\leq k\leq 10^9 0k109 1 ≤ a i ≤ 1 0 9 1\leq a_i\leq 10^9 1ai109

每个测试点的具体限制见下表:

测试点编号 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 24 4 4 4 1 0 9 10^9 109
5 ∼ 8 5\sim 8 58 100 100 100 1 0 9 10^9 109
9 ∼ 12 9\sim 12 912 1000 1000 1000 1 0 9 10^9 109
13 ∼ 16 13\sim 16 1316 1 0 6 10^6 106 1 0 9 10^9 109
17 ∼ 20 17\sim 20 1720 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;
    
}
  • 43
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

green qwq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值