来自学长的快乐AK题——Day8 洛谷月赛

洛谷月赛——分治+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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值