牛客IOI周赛23-普及组 /D(序列dp)

本文介绍了牛客IOI周赛中的一道普及组题目,涉及序列和最大公因数的概念。讨论了如何解决寻找最长优秀序列的问题,通过序列动态规划(dp)的方法,优化时间复杂度,并详细阐述了状态转移方程及其优化策略。

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

牛客IOI周赛23-普及组 /D(序列dp)

题目

题面:
小L喜欢数和数列。小L称a1…ana_1…a_na1​…an​这些数为优秀的。小L称一个序列b1…bmb_1…b_mb1​…bm​为好的当且仅当:
1.对于任意的 i(1≤i<m)i (1 \leq i <m)i(1≤i<m),满足 bi<bi+1b_i<b_{i+1}bi​<bi+1​。
2.对于任意的 i(1≤i<m)i (1 \leq i <m)i(1≤i<m),满足 gcd(bi,bi+1)>1gcd(b_i,b_{i+1})>1gcd(bi​,bi+1​)>1。其中,gcd(x,y)gcd(x,y)gcd(x,y) 为 xxx 和 yyy 的最大公因数,即最大的 ddd,满足:d∣xd|xd∣x且d∣yd|yd∣y。
3.对于任意的 i(1≤i≤m)i (1 \leq i \leq m)i(1≤i≤m),bib_ibi​这个数是优秀的。
现在,小L想知道最长的能称为好的的序列的长度是多少,容易证明这个长度是有穷的。

显然这是一个序列dp的问题:
先将序列从小到大排序,用 d p [ i ] dp[i] dp[i] i i i位合法序列的最大长度,
那么有转态转移方程。
d p [ i ] = max ⁡ j < i , g c d ( a [ i ] , a [ j ] ) ! = 1 ( d p [ i ] , d p [ j ] + 1 ) dp[i]=\max_{j<i,gcd(a[i],a[j])!=1}(dp[i],dp[j]+1) dp[i]=j<i,gcd(a[i],a[j])!=1max(dp[i],dp[j]+1)
时间复杂度为 O ( n 2 ) O(n^2) O(n2)

现考虑优化dp过程,原dp转移过程中需要枚举所有 j < i j<i j<i的项且要求解 g c d ( a [ i ] , a [ j ] ) gcd(a[i],a[j]) gcd(a[i],a[j])
可以考虑枚举 a [ i ] a[i] a[i]的质因子,用 l e n [ p ] len[p] len[p]表示包含素因子 p p p a [ i ] a[i] a[i]中能组成合法序列的最大长度。
那么状态转移方程变化为:
d p [ i ] = max ⁡ p ∣ a [ i ] ( d p [ i ] , l e n [ p ] + 1 ) dp[i]=\max_{p|a[i]}(dp[i],len[p]+1) dp[i]=pa[i]max(dp[i],len[p]+1) p p p a [ i ] a[i] a[i]的素因子。
在更新完 d p [ i ] dp[i] dp[i]后,我们需要更新 a [ i ] a[i] a[i]所有素因子对应的 l e n [ i ] len[i] len[i],即
l e n [ p ] = max ⁡ p ∣ a [ i ] ( l e n [ p ] , d p [ i ] ) len[p]=\max_{p|a[i]}(len[p],dp[i]) len[p]=pa[i]max(len[p],dp[i])

#include<bits/stdc++.h>
using namespace std;
const int max_n=1e6+5;
int dp[max_n];
int prime[max_n];
int vis[max_n];
int a[max_n];
int n;
int cnt=0;
vector<int> f;
void init(void)
{
	for(int i=2;i<max_n;i++)
	{
		if(!vis[i])prime[cnt++]=i;
		for(int j=0;j<cnt&&i*prime[j]<max_n;j++)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int main(void)
{
	int t;
	init();
	scanf("%d",&t);
	while(t--)
	{
		int ans=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
		sort(a+1,a+n+1); 
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
		{
			f.clear();
			int anss=-1;
			for(int j=0;j<cnt&&prime[j]*prime[j]<=a[i];j++)
			if(a[i]%prime[j]==0){
				while(a[i]%prime[j]==0)a[i]/=prime[j];
				anss=max(anss,dp[prime[j]]+1);
				f.push_back(prime[j]);
				if(a[i]==1)break;
			}
			if(a[i]!=1){
				anss=max(anss,++dp[a[i]]);
				f.push_back(a[i]);
			}
			ans=max(anss,ans);
			for(int k=0;k<f.size();k++)
			dp[f[k]]=max(dp[f[k]],anss);
		}
		cout<<ans<<endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值