【题解】P3147 [USACO16OPEN]262144 P(DP)

【题解】P3147 [USACO16OPEN]262144 P

虽然是道绿题,但我还是挂/卡了很久,是道好题,写篇题解记录一下。


题目链接

P3147 [USACO16OPEN]262144 P - 洛谷

P3146 [USACO16OPEN]248 G - 洛谷

下面这道题是上面的弱化版,只是数据范围不同,但有一种解法只能通过 P3147,所以这里都以 P3147 为例解释(P3147 的 AC 代码可以通过 P3146),但是会讲到两种解法。

题意概述

有一个长为 n(2n262144)n(2 \le n \le 262144) 的正整数序列,范围在 [1,40][1,40]。在一步中,可以选择相邻的两个相同的数,然后合并成一个比原来大一的数(例如两个 77 合成一个 88),最大化出现过的最大的数。

思路分析

真的是道妙题,单从线性 DP 的角度思考我愣是想不出来 DP 状态;而从区间 DP 的角度想又做法假了。这里记录线性 DP区间 DP 两种解法。

解法一:线性 DP

要合成一个数 ii,那么就要找到两个相邻的最大值为 i1i-1 的区间。

定义 dpi,jdp_{i,j} 表示左端点为 jj,能合并出 ii 的右端点的位置。

发现这玩意跟倍增有点相似。

那么有:

dp[i][j]=dp[i1][dp[i1][j]]dp[i][j]=dp[i-1][dp[i-1][j]]

画个图解释一下:

相当于是从 jj 开始找到合并出 i1i-1 的右端点位置,再从这个右端点位置往后找到从这个位置开始能合并出 i1i-1 的第二个右端点位置。

那么就相当于找到了两段相邻的最大值为 i1i-1 的区间,可以合并出 ii

这么妙的做法我怎么就想不到呢。/kk

那么我们只需要在刚开始输入时,初始化 dp[ai][i]=i+1dp[a_i][i]=i+1。(注意不是 ii 而是 i+1i+1

然后枚举 ii,在这道题中 aia_i 最大到 4040,这种倍增式合并,能合成的最大的 ii40+logn=40+log26214440+18=5840+ \log n=40+ \log 262144 \approx 40+18=58,所以只需要把 ii11 枚举到 5858 即可。

最后枚举 jjjj1n1-n

时间复杂度:O(58n)O(58 n)

解法二:区间 DP

注:这种解法是针对 P3146 的,由于 P3147 的数据范围较大,此种做法行不通。

相对于这个题的线性 DP,区间 DP 是更好想的。

看到“合并”,应该一眼想到区间 DP。

我当时心想:这题如果用区间 DP 未免也太简单了,然后立马开写,结果却 WA 了,只有 58pts58pts……我真菜。。

这里记录一下我的思路历程。

刚开始想,这题不就是石子合并(弱化版) - 洛谷的变形嘛。

dpl,rdp_{l,r} 表示区间 [l,r][l,r] 所能合成的最大值,在求解 dpl,rdp_{l,r} 时,枚举断点 kk,然后分类讨论。

  • dpl,k=dpk+1,rdp_{l,k}=dp_{k+1,r} 时,dpl,r=max(dpl,r,dpl,k+1)dp_{l,r}=\max(dp_{l,r},dp_{l,k}+1)。即:[l,k][l,k][k+1,r][k+1,r] 是两段能合成最大值相同的区间,答案即为两段的 dpdp+1+1

  • dpl,kdpk+1,rdp_{l,k} \ne dp_{k+1,r} 时,dpl,r=max(dpl,r,dpl,k,dpk+1,r)dp_{l,r}=\max(dp_{l,r},dp_{l,k},dp_{k+1,r})。即:若两段所能合成的最大值不相同,则区间 [l,r][l,r] 在所有这些区间中取最大值。

最后答案是 dp1,ndp_{1,n}。但是这样交上去却 WA 了,只有 58pts58pts,然后看了眼评论区发现我的做法假了。

Hack:

3
2 1 2

按照我上述思路:

在求 dp1,3dp_{1,3} 时,由于 dp1,1=dp2,3=2dp_{1,1}=dp_{2,3}=2,所以 dp1,3=3dp_{1,3}=3,但实际答案应该是 22

因为 dp2,3dp_{2,3} 的答案是继承了 dp3,3dp_{3,3},而这样的继承在这一次转移中是可以的,但当用 dp2,3dp_{2,3} 去计算 dp1,3dp_{1,3} 却由于 1133 不连续,所以得到的答案是错误的。

那么怎么做呢。

可以用一个变量 ansans 表示当前找到的最大值答案。

对于每个 dpl,rdp_{l,r},只有当 dpl,k=dpk+1,rdp_{l,k}=dp_{k+1,r} 的时候更新值,然后每次将 ansans 更新为当前最大即可。

注意 ansans 在初始输入时也要更新一遍。

时间复杂度:

代码实现

解法一

//luoguP3147
//线性 DP 解法 
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=262150;
int a[maxn],dp[65][maxn];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
 } 
 
int main()
{
	int n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		dp[a[i]][i]=i+1;
	}
	int ans=0;
	for(int i=2;i<=58;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(!dp[i][j])dp[i][j]=dp[i-1][dp[i-1][j]];
			if(dp[i][j])ans=i;
		}
	}
	cout<<ans<<endl;
	return 0; 
}
/*dp[i][j] 表示左端点为 j,能合并出 i 的右端点位置。 
那么 dp[i][j]=dp[i-1][dp[i-1][j]].
可以发现数字大小最大为 40+log n = 58.
这题怎么这么妙啊!我为什么就想不到! 
*/ 

解法二

//luoguP3146
//区间 DP 解法
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=255;
int dp[maxn][maxn],a[maxn];
int ans;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
 } 
 
int main()
{
	int n;
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();dp[i][i]=a[i];
		ans=max(ans,a[i]);
	}
	for(int len=2;len<=n;len++)
	{
		for(int l=1;l+len-1<=n;l++)
		{
			int r=l+len-1;
			for(int k=l;k<r;k++)
			{
				if(dp[l][k]==dp[k+1][r])dp[l][r]=max(dp[l][r],dp[l][k]+1);
				ans=max(dp[l][r],ans);
			}
//			cout<<l<<" "<<r<<" "<<dp[l][r]<<endl; 
		}
	}
	cout<<ans<<endl;
	return 0;
}
/*3
2 1 2
*/
posted @   向日葵Reta  阅读(103)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示