牛客网NOIP赛前集训营-提高组(第四场)B题 区间
牛客网NOIP赛前集训营-提高组(第四场)
题目描述
给出一个序列 a1, ..., an。
定义一个区间 [l,r] 是好的,当且仅当这个区间中存在一个 i,使得 ai 恰好等于 al, al+1, ..., ar-1, ar 的最大公因数。
求最长的好的区间的长度。
• 注意到:如果 𝑖 的区间扩展到了 𝑗,那么 𝑗 的区间一定是 𝑖 的区间的子区间(因为 𝑗 是 𝑖 的一个倍
数),从而不可能更新答案
• 我们可以考虑按一个顺序枚举 𝑖,每次只要一个点被区间经过,就可以不用扩展它的区间
• 按 𝑎𝑖 从小到大的顺序扩展 𝑖,这样每一个数最多被扩展过两次(从左和从右分别一次)
• 时间复杂度为排序复杂度 \(O(nlogn)\)
以上是官方给出的部分题解,也就是复杂度较高的题解。
照着思路写了一遍得到了90分。
这里需要注意一个问题,就是这里j被扩展到是有方向的,也就是说如果单纯的将被扩展之后的位置打上标记的话是错误的(虽然仍然能够得到90分,数据水。)
如下面的数据:
5
4 72 36 9 18
我们会先从4开始扩展,那么如果这个时候就给72 和 36打上标记的话,我们再从9扩展的时候就无法扩展到72和36。所以原本最长的答案4就会变成3。
观察怎么解决这个问题,我们考虑如果9在4右边在72和36的左边,那么就会出现4的区间会包括9的区间,那么把vis带上方向判断就可以了。
\(vis[i][0]\)表示i这个位置的点有没有被其左边的点更新过。
\(vis[i][1]\)表示这个位置的点有没有被其右边的点更新过。
\(O(nlogn)\)code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int wx=4000017;
inline char get_char(){
static char buf[1000001],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
#define short long long
inline short read(){
short num=0;
char c;
while(!isdigit(c=get_char()));
for(num=c-48;isdigit(c=get_char());num=((num+(num<<2))<<1)+c-48);
return num;
}
struct node{
int v,id;
friend bool operator < (const node & a,const node & b){
return a.v<b.v;
}
}a[wx];
int b[wx];
bool vis[wx][2];
int n,tot;
int Max(int a,int b){
if(a>b)return a;
return b;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].v;a[i].id=i;
b[i]=a[i].v;
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
int pos=a[i].id;
if(!vis[pos][1]||!vis[pos][0]){
int ans=0;
for(int j=pos;j<=n;j++){
if(!vis[j][1]&&b[j]%b[pos]==0){
vis[j][1]=1;ans++;
}
else break;
}
for(int j=pos-1;j>=1;j--){
if(!vis[j][0]&&b[j]%b[pos]==0){
vis[j][0]=1;ans++;
}
else break;
}
tot=Max(ans,tot);
}
}
printf("%lld\n",tot);
return 0;
}
还有O(n)做法。
首先对于这种题,逆向去找每个位置的数作为最小值的答案是肯定的。
那么怎么样线性去找呢?
问题转化成每个位置的数作为公因数时,求对应的l[i]和r[i]。
因为我们知道r[i]不一定是单调的,但是如果r[i]<r[i-1],那么r[i]不会对答案产生贡献。
那么我们想办法把这个东西强制变成单调的,又因为如果我们让r[i]=r[i-1],那么最极限的情况r[i]也不可能更新答案,所以对于r[i]<r[i-1]的情况,直接让r[i]=r[i-1],去把r[i]当做一个中间量递推求出后面的,就可以做到线性。
还是很难懂,看看代码就可以理解了。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int wx=4000017;
inline char get_char(){
static char buf[1000001],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
#define short long long
inline short read(){
short num=0;
char c;
while(!isdigit(c=get_char()));
for(num=c-48;isdigit(c=get_char());num=((num+(num<<2))<<1)+c-48);
return num;
}
long long a[wx];
int l[wx],r[wx];
int n;
int ans;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)for(l[i]=i;l[i]>1&&a[l[i]-1]%a[i]==0;l[i]=l[l[i]-1]);
for(int i=n;i>=1;i--)for(r[i]=i;r[i]<n&&a[r[i]+1]%a[i]==0;r[i]=r[r[i]+1]);
for(int i=1;i<=n;i++)ans=max(ans,r[i]-l[i]+1);
printf("%d\n",ans);
return 0;
}