依据值域的 \(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;
}

Luogu P4933 大师 题解 [ 绿 ] [ 线性 dp ] [ dp 细节处理 ] [ 限制转移条件优化 ]的更多相关文章

  1. Luogu P4933 大师(dp)

    P4933 大师 题意 题目背景 建筑大师最近在跟着数学大师ljt12138学数学,今天他学了等差数列,ljt12138决定给他留一道练习题. 题目描述 ljt12138首先建了\(n\)个特斯拉电磁 ...

  2. Luogu P4933 大师【dp】By cellur925

    题目传送门 题目大意:给你一个序列,求子序列为等差数列的子序列个数.序列长度$n<=2000$,最高的塔高$v<=20000$. 这种计数类的题,大概就是dp的套路了⑧.开始设计的是一个错 ...

  3. E - Max Sum Plus Plus Plus HDU - 1244 (线性区间DP)

    题目大意:  值得注意的一点是题目要求的是这些子段之间的最大整数和.注意和Max Sum Plus Plus这个题目的区别. 题解: 线性区间DP,对每一段考虑取或者不取.定义状态dp[i][j]指的 ...

  4. Luogu P1541 乌龟棋 【线性dp】

    题目背景 小明过生日的时候,爸爸送给他一副乌龟棋当作礼物. 题目描述 乌龟棋的棋盘是一行 N 个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第 N 格是终点,游戏要求玩家控制一个乌龟 ...

  5. 【题解】POJ1934 Trip (DP+记录方案)

    [题解]POJ1934 Trip (DP+记录方案) 题意: 传送门 刚开始我是这么设状态的(谁叫我DP没学好) \(dp(i,j)\)表示钦定选择\(i\)和\(j\)的LCS,然而你会发现这样钦定 ...

  6. 【题解】剪纸条(dp)

    [题解]剪纸条(dp) HRBUST - 1828 网上搜不到题解?那我就来写一篇吧哈哈哈 最优化问题先考虑\(dp\),设\(dp(i)\)表示将前\(i\)个字符(包括\(i\))分割成不相交的回 ...

  7. 【题解】地精部落(DP)

    [题解]地精部落(DP) 设\(f_i\)表示强制第一个是谷的合法方案数 转移枚举一个排列的最大值在哪里,就把序列分成了互不相干的两个部分,把其中\(i-1\choose j-1\)的数字分配给前面部 ...

  8. (线性结构dp )POJ 1260 Pearls

    Pearls Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 10558   Accepted: 5489 Descripti ...

  9. 【Luogu】P3239亚瑟王(概率DP)

    题目链接 请看luogu第一篇题解 #include<cstdio> #include<algorithm> #include<cstring> #include& ...

  10. LibreOJ 6177 题解(状压DP)

    题面 传送门 分析 刚看到这道题时想的是跟最短哈密顿路类似的二进制状压DP,先用floyd处理距离 但是此题用二进制不够,应该用三进制 0,1,2分别表示未送,正在送,已送完 dp[s][i]表示当前 ...

随机推荐

  1. redis之性能优化

    1 redis-cli命令的 --stat选项 关于stat选项,官网也是介绍的比较简单.使用redis-cli命令加上stat选项可以实时监视redis实例,比如当前节点内存中缓存的 key总数以及 ...

  2. 通过加密的方式做身份鉴权—Demo设计

    目录 鉴权方式 账号+密码 账号+短信验证码 第三方渠道鉴权--微信 Reference 本文只是一个Demo设计,仅供学习思路,并不能用于真实的线上业务,因为有很多漏洞. 一般线上应用都需要对用户身 ...

  3. Swagger 调试,我不想再复制粘贴token啦~

    作为后端开发,进行Web Api 调试,除了使用 Postman, Apifox 等 Web Api 调试工具之外,我想使用Swagger进行调试应该是更方便,更常用的方式了吧. 那么在需要 toke ...

  4. Consul 学习总结

    什么是Consul? Consul是一种服务网络解决方案,使团队能够管理服务之间以及跨本地和多云环境和运行时的安全网络连接.Consul提供服务发现.服务网格(service mesh).流量管理和网 ...

  5. DDCA —— 内存一致性

    1. 同步(Synchronization) 1.1 构造锁(Locks) 原子(atomic)执行:应用程序的某些部分必须独占执行(原子性),这意味着在这些部分执行期间,其他并行进程无法访问或修改相 ...

  6. 关于 JavaScript 中的 Array.reduce()

    关于 JavaScript 中的 Array.reduce() reduce() 方法在 MDN 的定义看起来很复杂: reduce((previousValue, currentValue, cur ...

  7. Netty有关

    https://www.baeldung.com/tag/netty/ https://github.com/eugenp/tutorials https://stackoverflow.com/qu ...

  8. .NET 9 中的 多级缓存 HybridCache

    HybridCache是什么 在 .NET 9 中,Microsoft 将 HybridCache 带入了框架体系. HybridCache 是一种新的缓存模型,设计用于封装本地缓存和分布式缓存,使用 ...

  9. Qt编写地图综合应用51-离线瓦片地图下载

    一.前言 写这个离线地图下载器的初衷,就是为了方便自己的几个需要离线地图的程序,客户需求,既然地图程序已经可以支持离线地图,那如何获取到这些离线瓦片地图文件是个关键,而且这是这个功能的关键,拿到这些一 ...

  10. Qt音视频开发48-通用通道管理

    一.前言 把通用的视频控件搞定以后,后期增加新的内核方便多了,不需要在好多个文件复制粘贴之类的,接下来就是需要一个统一的类来管理视频监控系统中的16个通道或者32个通道,甚至64个通道也有可能,当然, ...