依据值域的 \(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. python常用模块汇总

    os模块 os.remove() 删除文件 os.unlink() 删除文件 os.rename() 重命名文件 os.listdir() 列出指定目录下所有文件 os.chdir() 改变当前工作目 ...

  2. php-fpm相关操作

    php-fpm常用操作 一. php5.3.3之后使用新号方式控制php-fpm进程 INT, TERM 立即终止 QUIT 平滑终止 USR1 重新打开日志文件 USR2 平滑重启所有worker进 ...

  3. Vue第三方插件

    1.滚动条 vuescroll 引入插件 - 官网 <script src="https://unpkg.com/vuescroll"></script> ...

  4. Trino 436 - 使用教程(亲测,详细)

    第一章 Trino简介 1. Trino概述 Trino是一个分布式SQL查询引擎,旨在查询分布在一个或多个异构数据源上的大型数据集.如果使用的是数TB或数PB的数据,那么很可能使用的是与Hadoop ...

  5. ProWindow的控制按钮状态

    在使用ArcGIS.Desktop.Framework.Controls.ProWindow时,有网友发现,设置窗口调整模式后,窗口的控制按钮是不全的. ProWindow win = new Pro ...

  6. GPU服务器常见问题汇总

    目录 Q1.从启动盘安装时黑屏/屏幕卡住? Q2.1T固态硬盘Ubuntu系统磁盘分区策略: Q3.安装Ubuntu需要选择更新吗? Q4.安装Ubuntu后重启无法开机? Q5.首次开机的配置代码? ...

  7. 如何为在线客服系统的 Web Api 后台主程序添加 Bootstrap 启动页面

    背景 我在业余时间开发了一款自己的独立产品:升讯威在线客服与营销系统.这个系统的核心后台主程序,在最早期是完全没有页面,经常有朋友部署之后,一访问是 404,以为没有部署成功.我一看这肯定不行啊,可后 ...

  8. Qt编写地图综合应用2-迁徙图

    一.前言 在很多web系统中,尤其是大屏系统中,经常可以看到类似于飞机迁徙图的效果,这个在echart中也是最常用的一个效果,迁徙图既可以是一个飞机也可以是其他形状,然后有一条动态的移动轨迹来表示流向 ...

  9. 推荐一个windows系统的下载和安装的网址:win7之家

    win7之家:http://www.windows7en.com/ 精校 完整 极致 Windows系统下载仓储站HelloWindows :https://hellowindows.cn/

  10. Docker简易操作

    Docker简易操作 1.启动docker sudo docker run [OPTIONS] IMAGE [COMMAND] [ARG...] sudo docker run -d -p 8009: ...