Luogu P4933 大师 题解 [ 绿 ] [ 线性 dp ] [ dp 细节处理 ] [ 限制转移条件优化 ]

依据值域的 O(n2) 做法

这种做法只适用于这种值域小的题,下一种做法才是求等差数列的通解。

我们定义 f[i][j] 表示以 hi 为最后一个数,公差为 j 的等差数列(长度 2 ) 的个数。

接下来我们找每一个数前面的数,确定公差后转移即可。时间 O(n2)

细节

表面上这道题很简单,但实际上细节非常多,一不小心就会挂,更何况这题的样例还比较大,不好调试。

首先,我们要把长度为 1 的等差数列个数记录到 ans 中(不加到 dp 数组里),即 n ,因为我们后面不会在考虑这些特殊的等差数列。

其次,我们在转移到时候,看似会这样写:

f[i][h[i]h[j]]=f[i][h[i]h[j]]+f[j][h[i]h[j]]

(省略本题原本要加的负数偏移量和取模操作)

其实并不对,因为我们转移的是它前面长度大于 1 的等差数列,在当时这些长度为 1 的不用算,但一旦转移到我们这里,这些长度为 1 的序列长度就变成 2 了,因此是要计算的。

考虑到每次转移,原先长度为 1 的只有一个,我们只要在转移的时候 +1 即可。

f[i][h[i]h[j]]=f[i][h[i]h[j]]+f[j][h[i]h[j]]+1

还有一个细节,就是在统计答案的时候,能否是:

ans=ans+f[i][j]

实际上不行,因为每次遍历 j 如果都加一遍 f[i][j] ,那么如果在 i 前面有两个数一样,就会导致公差相同,这时候加 f[i][j] ,就会导致先更新的那种情况被加了两次,导致答案偏大。

解决办法就是把 ans 和 dp 数组一起转移:

f[i][h[i]h[j]]=f[i][h[i]h[j]]+f[j][h[i]h[j]]+1

ans=ans+f[j][h[i]h[j]]+1

于是就好了。

注意要取模,由于公差可能是负数,所以还要加一个偏移量。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353,eps=40005;
int n,h[1005];
ll ans=0,f[1005][100005];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		ans=(ans+1)%mod;
		cin>>h[i];
		for(int j=1;j<i;j++)
		{
			f[i][h[i]-h[j]+eps]=(f[i][h[i]-h[j]+eps]+f[j][h[i]-h[j]+eps]+1)%mod;
			ans=(ans+f[j][h[i]-h[j]+eps]+1)%mod;
		}	
	}
	cout<<ans%mod;
	return 0;
}

如果你不想考虑这么多,那么在全部转移完后再遍历一遍 dp 数组求方案也是可以的,这样就可以让 ans 直接加 f[i][j] 了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353,eps=40005;
int n,h[1005];
ll ans=0,f[1005][100005];
int main()
{
	cin>>n;
	ans=n;
	for(int i=1;i<=n;i++)
	{
		cin>>h[i];
		for(int j=1;j<i;j++)
		{
			f[i][h[i]-h[j]+eps]=(f[i][h[i]-h[j]+eps]+f[j][h[i]-h[j]+eps]+1)%mod;
		}	
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=100000;j++)
		{
			ans+=f[i][j];
			ans%=mod;
		}
	}
	cout<<ans%mod;
	return 0;
}

基于数的多少的 O(n2)~O(n3) 的做法

定义 f[i][j] 表示前一项为 hi ,最后一项为 hj 的等差数列的个数。

转移的细节和上面基本一样,就不说明了。

很自然的转移方程:

f[j][i]=1+f[k][j](h[i]h[j]=h[j]h[k])

时间 O(n3) ,常数为 16 ,稳过 103 数据。

优化就是把每个数的下标存进 unordered_map<int,vector<int>> 里,转移的时候避免多转移即可。

理想状态为 O(n2) ,要是被卡哈希或者构造所有数相同的数据可能会到 O(n3) 甚至带更大的常数。但概率比较小。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
int n,h[1005];
ll f[1005][1005],ans=0;
unordered_map<int,vector<int> >m;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>h[i];
		m[h[i]].push_back(i);
	}
	ans=n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<i;j++)
		{
			f[j][i]=1;
			vector<int>v=m[h[j]-(h[i]-h[j])];
			for(int k=0;k<v.size()&&v[k]<j;k++)
			{
				int frm=v[k];
				f[j][i]+=f[frm][j];
				f[j][i]%=mod;
			}
			ans+=f[j][i];
			ans%=mod;
		}
	}
	cout<<ans%mod;
	return 0;
}
posted @   KS_Fszha  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示