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

依据值域的 \(O(n^2)\) 做法

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

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

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

细节

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

首先,我们要把长度为 \(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(n^2)\)~\(O(n^3)\) 的做法

定义 \(f[i][j]\) 表示前一项为 \(h_i\) ,最后一项为 \(h_j\) 的等差数列的个数。

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

很自然的转移方程:

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

时间 \(O(n^3)\) ,常数为 \(\frac{1}{6}\) ,稳过 \(10^3\) 数据。

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

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

代码

#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 @ 2024-07-23 15:33  KS_Fszha  阅读(3)  评论(0编辑  收藏  举报