洛谷U525322 优美区间
优美区间
题目描述
有一个长度为 n n n 的数字序列,序列的第 i i i 个数为 a i a_i ai。
定义区间 [ l , r ] [l,r] [l,r] 的优美程度为 gcd ( a l , a l + 1 , … , a r ) × ∑ i = l r a i \gcd(a_l,a_{l+1},\dots,a_r)\times\sum\limits_{i=l}^ra_i gcd(al,al+1,…,ar)×i=l∑rai。
你需要求出长度至少为 k k k 的区间的优美程度的最大值。
输入格式
第一行两个正整数 n , k n,k n,k。
第二行 n n n 个正整数,第 i i i 个正整数为 a i a_i ai。
输出格式
一行一个整数,表示答案。
样例 #1
样例输入 #1
7 2
2 1 5 4 4 4 2
样例输出 #1
48
提示
1 ≤ k ≤ n ≤ 1 0 6 1\le k\le n\le10^6 1≤k≤n≤106, 1 ≤ a i ≤ 1 0 6 1\le a_i\le10^6 1≤ai≤106。
注意到:对于一个 r r r,不同的 gcd ( a l , a l + 1 , … , a r ) \gcd(a_l,a_{l+1},\dots,a_r) gcd(al,al+1,…,ar) 至多只有 ⌊ log a r ⌋ + 1 \lfloor\log a_r\rfloor+1 ⌊logar⌋+1 种。
当我们对于一个 r = x r=x r=x 求出了所有不同的 gcd ( a l , a l + 1 , … , a r ) \gcd(a_l,a_{l+1},\dots,a_r) gcd(al,al+1,…,ar) 并找到了与之对应的最小的 l l l 后,要求出 r = x + 1 r=x+1 r=x+1 的情况。只需要在 l l l 的集合中插入 x + 1 x+1 x+1,并将对应的 gcd \gcd gcd 相同的 l l l 只保留最小的即可。
于是对于每个 r r r,我们都可以求出所有不同的 gcd ( a l , a l + 1 , … , a r ) \gcd(a_l,a_{l+1},\dots,a_r) gcd(al,al+1,…,ar) 和与之对应的最小的 l l l,依次对这些区间中长度至少为 k k k 的区间计算优美程度,并取最大值即可。
时间复杂度为 Θ ( n log 2 a ) \Theta(n\log^2 a) Θ(nlog2a),常数较小,可以通过本题。精细实现或使用科技可以做到 Θ ( n log a ) \Theta(n\log a) Θ(nloga) 的复杂度。
暴力思路
常规暴力思路很容易想,但是拿不到满分。即遍历每一个可能的区间,同时计算gcd。
但是这样时间复杂度是
O
(
n
2
log
a
)
O(n^2 \log a)
O(n2loga)级的。
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
typedef long long ll;
using namespace std;
int a[1000005],p[1000005];
// int g[1000005];
signed main() {
cin.tie(0)->ios::sync_with_stdio(0);
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
p[i]=a[i]+p[i-1];
}
int ans = 0;
for (int i = 1; i <= n - k + 1; i++) {
int g = a[i];
for (int j = i; j <= n; j++) {
g = __gcd(g, a[j]);
if (j - i + 1 >= k) {
int sum = p[j] - p[i - 1];
int tans = g * sum;
ans = max(ans, tans);
}
}
}
cout << ans << endl;
return 0;
}
AC代码:
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
typedef long long ll;
using namespace std;
int a[1000005],p[1000005];
int g[1000005]; //存储左端点gcd
int pos[1000005]; //存储左端点位置
signed main() {
cin.tie(0)->ios::sync_with_stdio(0);
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
p[i]=a[i]+p[i-1]; // 前缀和
}
int ans = 0;
int cnt = 0;
for (int i = 1; i <= n; i++) { // 枚举右端点i
pos[++cnt]=i; // 将右端点本身添加进左端点的集合中
g[cnt]=a[i];
int tcnt = cnt;
cnt = 0;
for (int j = 1; j <= tcnt; j++) {//枚举左端点j
g[j]=__gcd(g[j],a[i]); // 每个左端点与a[i]gcd
if(g[j]!=g[cnt]){ // 重新计数 保存每个gcd最小左端点信息
pos[++cnt]=pos[j];
g[cnt]=g[j];
if ( i - pos[j] + 1 >= k) { // 长度满足条件就更新答案
int sum = p[i] - p[pos[j]-1];
int tans = g[j] * sum;
ans = max(ans, tans);
}
}
}
}
cout << ans << endl;
return 0;
}