牛客网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; }
- 牛客网NOIP赛前集训营-提高组(第四场)游记
- XJOI NOIP16提高组赛前训练18-day2 T1:友好数对(数论)
- 2018年牛客网NOIP赛前训练营游记
- Codevs1154 能量项链 ——2006年NOIP全国联赛提高组 区间dp
- 厦门一中“炫动杯”NOIP2011 第四场模拟赛 提高组
- XJOI NOIP16提高组赛前训练19-day1 T1:迷宫(bfs)
- XJOI NOIP16提高组赛前训练17 T1:GotoAndPlay(二分图染色)
- NOIP 2010 - 提高组 引水入城 棋盘上的BFS+区间覆盖(贪心) 重庆一中高2018级竞赛班第十次测试 2016.9.16 Problem 4
- JZOJ 100035. 【NOIP2017提高A组模拟7.10】区间
- XJOI NOIP16提高组赛前训练19-day1 T2:过路费(SPFA)
- NOIP16提高组赛前训练20-day2 T2:种花 flower(组合数学)
- XJOI NOIP16提高组赛前训练17 T2:StopAllSounds(DP)
- {小结}2016.07.11【初中部 NOIP提高组 】模拟赛C
- NOIP 2011 提高组 复赛 day2 bus 观光公交
- NOIP2014提高组模拟题 8.9
- 2017.1.15【初中部 NOIP提高组】模拟赛B组 七天使的通讯(angelus) 题解
- [NOIP2005][提高组T2]过河
- 最优贸易 2009年NOIP全国联赛提高组(最短路)
- 【NOIP2016提高A组模拟9.24】天使的分裂
- {小结}2016.07.14【初中部 NOIP提高组 】模拟赛B