P9032 [COCI2022-2023#1] Neboderi 题解
前言
校内模拟赛考了,于是写篇题解讲自己的做法。
题目链接。
题目大意
给出一个长度为
基本思路
赛时看到题目想到的是枚举
如果
因为
优化
首先要滚动数组,滚去
其次
还有枚举
接着注意到
- 可以使用时间戳优化:更新
dp(j) 时将其时间戳(最近更新时间)tag(j)\gets i 。转移时如果tag(j)=i-1 ,则dp(j)\gets dp(j)+1 ,反之dp(j)\gets 1 。这也是代码中的实现方式 - 也可以直接判断是否满足
j\mid a_{i-1} ,如果是,则i-1 时必然更新过dp(j) ,反之没有。
时间复杂度
不懂可以看代码。
代码
还是很简洁的,压了行。
#include <iostream>
#include <vector>
using std::cin;
typedef long long ll;
constexpr int N=1e6+114514;
int n,k,V;
int a[N];
ll s[N]; // 前缀和
int dp[N],tag[N]; // dp 数组和时间戳
std::vector<int> e[N];
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>k; // 求前缀和、值域大小
for(int i=1;i<=n;i++)cin>>a[i],s[i]=s[i-1]+a[i],V=std::max(V,a[i]);
// 枚举倍数求约数。e[x] 表示 x 的约数
for(int i=1;i<=V;i++)for(int j=1;i*j<=V;j++)e[i*j].push_back(i);
ll ans=0;
for(int i=1;i<=n;i++)for(int j:e[a[i]])
{
if(tag[j]==i-1)dp[j]++; // 如果上次有更新(没清 0)则累加
else dp[j]=1; // 反之上次会清 0,置成 1
tag[j]=i; // 更新时间戳
if(dp[j]>=k)ans=std::max(ans,(s[i]-s[i-dp[j]])*j); // 更新答案
}
printf("%lld",ans);
return 0;
}