洛谷月赛——分治+RMQ
Description
数据规模
思路
首先答案一定是最大值的约数d,这些数不多我们可以枚举这些数 。然后我们称d的倍数的位置为关键点。
现在我们要求在所划分区间的最大值全都是关键点的情况下,最多能够划分的区间的数量。
我们每次递归时,找出当前准备划分的区间中最大的数(第一次肯定是最大的那个数),然后如果这个数是d的倍数,就递归左右两边继续划分。
如果不是,设当前递归到的区间为l~r,当前区间的最大值下标为p,那么当前这个最大值连同l~p-1或p+1~r其中的某个区间就必须被合并到另一个最大值为关键点的区间(否则当前区间l~r的最大值就不是一个关键点)。
我们就同时向最大值的左右两边递归(前提是那一边有最大值为关键点的区间),比较这个点是连同左边的l~p-1的区间被合并更优,还是连同右边的p+1~r的区间被合并更优(因为在该节点前被划分的区间肯定有比当前节点更大的最大值,因此无需考虑谁大谁小)。
用RMQ维护区间最大的位置即可。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e6,P=20;
int n,k,d[N],a[N],st[N][P+1],cnt,maxn;//st表中为区间最大值的编号
int dfs(int l,int r)
{
if(l>r) return 0;
int step=log2(r-l+1);
int now=a[st[l][step]]>a[st[r-(1<<step)+1][step]]?st[l][step]:st[r-(1<<step)+1][step];
if(a[now]%d[cnt]==0)//当前区间的最大值为d的倍数,则左右区间无须合并,贡献都计入答案
return dfs(l,now-1)+dfs(now+1,r)+1;
else//当前区间的最大值不是d的倍数,需要将其并入某个区间
{
//如果区间的左边没有数合并,就只能往右边合并,就无须向右边走
if(l==1) return dfs(l,now-1);
//如果区间的右边没有数合并,就只能往左边合并,就无须向左边走
if(r==n) return dfs(now+1,r);
//否则取左右区间中较小的去合并,留下产生贡献更多的区间计入答案
int rec=0;
rec=max(rec,max(dfs(l,now-1),dfs(now+1,r)));
return rec;
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
maxn=max(maxn,a[i]);
st[i][0]=i;
}
for(int i=1;i<=P;i++)
for(int j=1;j<=n;j++)
st[j][i]=a[st[j][i-1]]>a[st[j+(1<<(i-1))][i-1]]?st[j][i-1]:st[j+(1<<(i-1))][i-1];
for(int i=1;i*i<=maxn;i++)//预处理最大值的约数
{
if(maxn%i) continue;
d[++cnt]=i;
if(maxn/i!=i) d[++cnt]=maxn/i;//要特判完全平方数
}
sort(d+1,d+cnt+1);
for(;cnt>=1;cnt--)
if(dfs(1,n)>=k)//大于k个子段一定能合并成k个子段,因此大于k也合法
{
printf("%d",d[cnt]);
break;
}
return 0;
}