0%

[LGR-128] 不见故人

什么?只是一道蓝题?

什么?我tm不会做?

什么?你告诉我是普及月赛T3?

题目链接

题意

给定一个长度为 的序列 ,可以进行如下操作若干次:

  1. 选定一个区间 ,令
  2. 令所有的

一次操作的代价是 为给定常数。

求将所有数字变得相同的最小总代价。

题解

这篇题解用到了一个叫做 baka's trick 的技巧。

玛卡巴卡(

首先这道题还是要分析一些性质的。

性质 :最终所有数字一定都是

显然,第 个位置上的数字,最终一定是 的因数,而所有数字都要相等,所以他们必然都是所有数字的最大公倍数。

性质 :所有位置最多参与一次操作。

这个想一想大概也能想出来。

同一个位置被操作两次,显然不如取两次操作的并,直接操作一次。

这样我们大概就能设计出一个 dp:

表示区间只涉及 的位置,并将前 个位置都变成 的最小总代价。

考虑转移。

若初始就有 ,那么 可以不进行操作,就有转移

否则,枚举最右侧的操作区间为 ,一定有

暴力转移,时间复杂度为 ,可以拿到 分。

,并对于每一个 ,二分出最大的,合法的 ,可以替代上面的转移:

时间复杂度 ,可以拿到 分。

baka's trick

不难发现,时间复杂度瓶颈主要在于二分与 gcd。

那么能不能加速这个寻找 的过程呢?

首先会发现,当 增长时, 单调不降,首先考虑双指针。

不难发现,我们双指针过程中,加入一个元素复杂度为 ,但是删除需要 ,很难蚌。

这个时候,就需要一个技巧,就是 baka's trick,不带删的双指针。

普通的双指针,我们会维护三个值 ,表示当前区间为 ,值为

进行 baka's trick 时,我们再额外维护一个值 ,并对于所有的 ,维护出

初始时,有

当右端点右移时,我们可以在 时间内更新出新的

使用 可以在 时间内算出当前区间 的权值。

当左端点左移时,若有 ,则令 ,并重新更新

可以证明,这个复杂度是均摊 的。

这样,我们就将寻找 的复杂度变为了 ,就可以进行我们上边的dp了。

总复杂度 ,可以通过这道题。

代码

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
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
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;
}
ll dp[4000001];
ll f[4000001];
int a[4000001];
int v[4000001];
int l,r,mid;
int base;
int n,k;
inline int gcd(int a,int b)
{
int r=a%b;
while(r)
{
a=b;
b=r;
r=a%b;
}
return b;
}
inline void update()
{
int now=0;mid=r;
for(int i=r;i>=l;i--) v[i]=now=gcd(now,a[i]);
}
inline int get(int l,int r){ return gcd(v[l],v[r]);}
int main()
{
n=read(),k=read();
for(int i=1;i<=n;i++) a[i]=read(),base=gcd(base,a[i]);
l=mid=1;
for(int i=1;i<=n;i++)
{
r=i,v[r]=gcd(v[r-1],a[i]);
if(a[i]==base)
{
dp[i]=min(dp[i-1],f[i-1]+i+k);
f[i]=min(f[i-1],dp[i]-i);
continue;
}
while(get(l,r)==base)
{
l++;
if(mid<l) update();
}
if(l==1) dp[i]=inf;
else dp[i]=f[l-2]+i+k;
f[i]=min(f[i-1],dp[i]-i);
}
printf("%lld\n",dp[n]);
return 0;
}

感谢观看!