【bzoj1978】【BeiJing2010】取数游戏 game【递推】

该博客讨论了一个关于取数的游戏,其中每个后续数字必须与前一个数字的最大公约数大于等于给定值L。博主介绍了如何使用动态规划来解决这个问题,通过优化状态转移方程,将时间复杂度降低到O(n^2)。样例输入和输出展示了算法的正确性,并提供了问题规模的限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

设有 NNN 个正整数,记为 a1,a2,...,ana_1,a_2,...,a_na1,a2,...,an。现依次取数,第一个数可以 随意取。假设目前取得 aja_jaj,下一个数取ak(j<k)a_k(j<k)ak(j<k),则aka_kak必须满足(aj,ak)≥L(a_j,a_k)\ge L(aj,ak)L(这里括号是求最大公约数的简记)。 求最多可取几个数。
Input
第一行是两个正整数NNNLLL
第二行是NNN个以空格分开的正整数a1,a2,...,ana_1,a_2,...,a_na1,a2,...,an.
Output
仅一行,一个数,表示按上述取法,最多可以取的数的个数。
Sample Input
5 6
7 16 9 24 6
Sample Output
3
HINT
选取 333 个数 16,24,6.  (16,24)=8,(24,6)=616,24,6.\;(16,24)=8,(24,6)=616,24,6.(16,24)=8(24,6)=6.
2≤L≤ai≤106,∀i2\le L\le a_i\le 10^6,\forall i2Lai106,i
30%30\%30% 的数据 N≤1,000N≤1,000N1,000
100%100\%100% 的数据 N≤50,000N\le 50,000N50,000

本蒟蒻一开始想了一个O(n2log⁡n)O(n^2\log n)O(n2logn)的算法:设F(i)F(i)F(i)表示第 iii 个数取的情况下从a1,a2,⋯ ,aia_1,a_2,\cdots, a_{i}a1,a2,,ai中所能取的数的个数的最大值,则
F(i)=1+max⁡1≤i′<iF(i′)F(i)=1+\max_{1\le i'<i}F(i')F(i)=1+1i<imaxF(i)
其中 i′i'i 满足(ai′,ai)≥L,F(1)=1(a_{i'},a_i)\ge L,F(1)=1(ai,ai)L,F(1)=1
看了hzwer的题解感觉十分精妙……
由于(ai′,ai)∣ai(a_i',a_i)\mid a_i(ai,ai)ai,故只需考虑aia_iai的约数能整除的ai′a_{i'}ai。不妨设
lasti(x)=max⁡1≤i′<ii′last_i(x)=\max_{1\le i'<i}i'lasti(x)=1i<imaxi
其中 i′i'i 满足 x∣ai′x\mid a_{i'}xai.
现在F(i)F(i)F(i)可改写为 F(i)=max⁡x∣ai,x≥L{F(lasti(x))}+1F(i)=\max_{x\mid a_i,x\ge L}\{F(last_i(x))\}+1F(i)=xai,xLmax{F(lasti(x))}+1.
状态数O(n)O(n)O(n),转移O(n)O(\sqrt n)O(n),总时间复杂度O(nn)O(n\sqrt n)O(nn).
代码实现中会随着i的递增不断更新last数组,这样last就不用开成222维了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;
const int maxn=50001;
int f[maxn];
int last[1000001];
int a[50001];
inline int read(){
	int x=0;scanf("%d",&x);return x;
}
int n,l,ans;
int main(){
	n=read();l=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i){
		int &tmp=f[i];
		for(int j=1;j*j<=a[i];++j){
			if(a[i]%j) continue;
			if(j>=l&&f[last[j]]+1>tmp) tmp=f[last[j]]+1;
			if(a[i]/j>=l&&f[last[a[i]/j]]+1>tmp) tmp=f[last[a[i]/j]]+1;
			last[j]=i;
			last[a[i]/j]=i;
		}
		if(tmp>ans) ans=tmp;
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值