4月10号dp专项补题(D题学完状压DP后再补)

本文解析了三道算法竞赛题目:寻找最长整除子序列、等比数列的三元组计数以及积木破坏回合数预测。通过图论、倍增法、哈希表和动态规划等技巧,提供了高效解决方案。

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

A - Clique in the Divisibility Graph (*1600)

在这里插入图片描述
——题意——
给出一个长度为n的数字集合{an}(给出时已满足单调增),要求从中找出一个最长的子序列,该子序列满足后一个数字正好对前一个数字取模为0,输出这个子序列的长度。样例中的序列为3、6、18或4、8、24.
其中,序列长度1<=n<=1e6,序列中元素大小1<=ai<=1e6.

——题解——
这题我是看成图论做的,对于每一个数字作一条有向边连向它的倍数,然后遍历一遍这张图,找出最长的链,最终由于复杂度太高而TLE。当一道题一下子没有思路的时候,不妨看看它的数据范围。这题中集合元素大小1<=ai<=1e6,也就是说我们可以记录每一个1e6以内的数字是否出现过。然后利用倍增法查找,比如3、6、9、12的找。如果某个正好倍增到某个出现过的数字,那么当前这个位置及其之前的最长子序列dp[ai * k]为该点已经记录的序列长度dp[ai * k]或 用于倍增数字的序列长度dp[ai]加上1,不断更新,最终遍历一遍集合中以所有元素为结尾的子序列,找到最大值就可以了。

——Code——

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int n;
int a[1000006];
int dp[1000006];
int pos[1000006];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		dp[i]=1;
		scanf("%d", &a[i]);
		pos[a[i]]=i;
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=2*a[i];j<=a[n];j+=a[i])
		{
			int k=pos[j];
			if( k==0 ) continue;
			dp[k]=max(dp[k], dp[i]+1);
		}
	}
	int ans=0;
	for(int i=1; i<=n; ++i)
		ans=max(ans, dp[i]);
	printf("%d\n", ans);
	return 0;
} 

B - Geometric Progression (*1700)

在这里插入图片描述
——题意——
和上题类似,给出一个长度为n的序列{an},和一个基数k,找出序列中所有长度为三的序列,满足三个元素呈等比关系,公比为k。其中,序列长度1<=n<=2e5,公比1<=k<=2e5,序列元素-1e9<=ai<=1e9。

——题解——
由题中数据范围可以分析出,2e9范围内只有2e5个元素,并且可重复,说明序列中的数字很“ 稀疏 ”,可以考虑用map来记录每个数字出现的次数cnt[ai]。对于序列中的一个数字ai,它所确定的三元序列数即为它左边值为ai/k的个数乘以右边值为ai * k的个数。

——Code——

#include<iostream>
#include<map> 
#include<cstdio>
#include<algorithm>
using namespace std;
map<long long,long long> cntl,cntr;
int n;
long long k;
int a[200005];
int main()
{
	scanf("%d %lld",&n,&k);
	for(int i=1;i<=n;++i)
	{
		scanf("%lld",&a[i]);
		++cntr[a[i]];
	}
	long long ans=0;
	for(int i=1;i<=n;++i)
	{
		--cntr[a[i]];
		if(!(a[i]%k)) ans+=cntl[a[i]/k]*cntr[a[i]*k];
		++cntl[a[i]];
	}
	printf("%lld\n",ans);
	return 0;
}

C - Bear and Blocks (*1800)

在这里插入图片描述
——题意——
给出一个长度为n的序列{hn},hi代表积木的高度,每次只有上左右都有积木的积木无法被破坏,其它积木都会被Bear在一回合内破话,问这些积木几回合被全部破坏。其中1<=n<=1e5,1<=hi<=1e9.

——题解——
这是一道挺有意思的思维题,关键在于怎么表达出破坏积木这一过程。实际上,可以把破坏看成两个部分,从左到右和从右到左,即先从左到右移除一些积木,在从右到左移除一些方块。破坏次数即为前者被破坏的次数加1和自身高度中的较小者,即dp[i]=min(dp[i-1]+1,h[i]),从右和从左各进行一次。

——Code——

#include<iostream>
#include<cstdio>
using namespace std;
int n;
int h[100005];
int dp[100005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d",&h[i]);
	for(int i=1;i<=n;++i)
		dp[i]=min(h[i],dp[i-1]+1);
	for(int i=n;i>=1;--i)
		dp[i]=min(dp[i],dp[i+1]+1);
	int ans=-1;
	for(int i=1;i<=n;++i)
		ans=max(dp[i],ans);
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值