Luogu P4933 大师 题解 [ 绿 ] [ 线性 dp ] [ dp 细节处理 ] [ 限制转移条件优化 ]
依据值域的 \(O(n^2)\) 做法
这种做法只适用于这种值域小的题,下一种做法才是求等差数列的通解。
我们定义 \(f[i][j]\) 表示以 \(h_i\) 为最后一个数,公差为 \(j\) 的等差数列(长度 \(\ge 2\) ) 的个数。
接下来我们找每一个数前面的数,确定公差后转移即可。时间 \(O(n^2)\) 。
细节
表面上这道题很简单,但实际上细节非常多,一不小心就会挂,更何况这题的样例还比较大,不好调试。
首先,我们要把长度为 \(1\) 的等差数列个数记录到 \(ans\) 中(不加到 dp 数组里),即 \(n\) ,因为我们后面不会在考虑这些特殊的等差数列。
其次,我们在转移到时候,看似会这样写:
(省略本题原本要加的负数偏移量和取模操作)
其实并不对,因为我们转移的是它前面长度大于 \(1\) 的等差数列,在当时这些长度为 \(1\) 的不用算,但一旦转移到我们这里,这些长度为 \(1\) 的序列长度就变成 \(2\) 了,因此是要计算的。
考虑到每次转移,原先长度为 \(1\) 的只有一个,我们只要在转移的时候 \(+1\) 即可。
还有一个细节,就是在统计答案的时候,能否是:
实际上不行,因为每次遍历 \(j\) 如果都加一遍 \(f[i][j]\) ,那么如果在 \(i\) 前面有两个数一样,就会导致公差相同,这时候加 \(f[i][j]\) ,就会导致先更新的那种情况被加了两次,导致答案偏大。
解决办法就是把 \(ans\) 和 dp 数组一起转移:
于是就好了。
注意要取模,由于公差可能是负数,所以还要加一个偏移量。
代码
#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\) 的等差数列的个数。
转移的细节和上面基本一样,就不说明了。
很自然的转移方程:
时间 \(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;
}