题目大意

求长度为 \(N\) 的排列使得 \(\sum_{i = 1}^{N - 1} |A_{p_{i + 1}} - A_{p_i}| \leq L\) 有多少个,对 \(10^9 + 7\) 取模。

\(1 \leq N \leq 100\) ,\(1 \leq L, A_i \leq 1000\) 。

思路分析

对于此题比较简单的能看出是插入 dp(分析及做题经验详见插入 dp 学习笔记),定义也简单,难在如何优化。

先来做暴力 dp ,首先因为绝对值不好处理,所以要排序(从大到小、从小到大都行,笔者是从大到小),然后根据插入 dp 的模板定义 \(dp_{i, j}\) 表示将前 \(i\) 个数插入了序列中,形成了 \(j\) 个连续段的方案数,同时此题要求和不超过 \(L\) ,所以再加一维 \(k\) 表示目前和为多少,即 \(dp_{i, j, k}\) 表示 \(i\) 个数,\(j\) 个段,和为 \(k\) 。

考虑转移,发现我们将 \(i\) 插入序列中时对于相邻 \(0/1/2\) 个数是不知道的,而这些数要么是以前加的要么是未来要加的,于是进行费用提前计算。

在插入 \(i\) 时判断 \(i\) 是新建 \(/\) 拓展 \(/\) 合并,分别对应贡献 \(2 A_i / 0 / -2 A_i\) ,为什么呢?因为新建意味着 \(i\) 左右两边要插的数都在未来,我们又排了序,所以都比 \(A_i\) 小,贡献 \(2 A_i\) ;拓展说明一边比 \(A_i\) 小,一边比 \(A_i\) 大,贡献抵消;合并说明两边都已安排好了,都比 \(A_i\) 大,要减去 \(A_i\) ,所以贡献 \(-2 A_i\) 。但——真的是这样吗?仔细考虑可以发现若加在首尾,则有一边是没有的,所以还要在状态中加两维 \(0/1\) 表示首尾确定 \(/\) 没确定,同时在转移中按贡献计算方法特殊处理即可。

恭喜你,成功 \(\text{TLE}\) or \(RE\) or \(MLE\) 了此题,在 \(k\) 这一维中,由于贡献存在负数,所以可能加得很大很大再减回来,或是减成负数又加回可接受范围,聪明的你肯定想到加偏移量吧,再次恭喜你,时空来到 \(O(N^3V)\) ,即使滚动时间也会爆炸,所以要对此进行优化。

转移优化

我们发现问题出在 \(k\) 这一维有正有负,所以 \(\leq L\) 这个限制没起到作用,若全是正数的话就可以不管 \(k > L\) 的状态了,那怎么才能做到呢?

如果你对费用提前计算中正负统一这一类优化经验丰富,那你肯定能想到差分优化,将我们在首尾一加一减两次贡献拆成若干个段之和,即将化为原序列差分数组,通过把 \(A_i - A_j\) 变为 \(\sum_{k = i}^{j - 1} A_k - A_{k + 1}\) 来去掉负号,果然!我束手无策了……

符号去掉了,但如何维护新的这一求和又成了个问题(费用提前计算跟没计算一样QAQ),转换一下思路,我们不对于 \(j\) 求两边的 \(\sum\) ,我们分析 \(A_i - A_{i + 1}\) 对总和的贡献。可以发现在状态 \(dp_{i, j, k, p, q}\) 时,无论 \(j\) 个段什么时候合并,最终都会变成 \(1\) 个段,并且都是靠第 \(> i\) 个数合并的,这意味着什么,这意味着 \(j - 1\) 次合并时求 \(\sum\) 一定包含有 \(A_i - A_{i + 1}\) 这一项啊!同时对于首尾,若还可以加,就会在以后关闭时计算到 \(A_i - A_{i + 1}\) 。所以从此状态无论转移到哪一个状态时,都 \(k \leftarrow k + 2(j - 1)(A_i - A_{i + 1}) + p + q\) 。

形式化的说,假设 \(j\) 个连续段中第 \(o\) 个段和第 \(o + 1\) 个段会在加入第 \(u_o\) 个数时合并,那我们对于这 \(j\) 个段对总和的贡献即为 \(\sum_{o = 1}^{j - 1} \sum_{v = i}^{u_o - 1} 2(A_v - A_{v + 1})\) (首尾特殊情况特殊处理),转化一下变成 \(\sum_{v = i}^n 2(A_v - A_{v + 1}) \sum_{o = 1}^{j - 1} [v \leq u_o - 1]\) ,我们发现 \(\sum_{o = 1}^{j - 1} [v \leq u_o - 1]\) 在 \(dp_v\) 这一层时 \(v > u_o - 1\) 的都已经被合并了,所以处于 \(dp_v\) 这一层时上式变成了 \(dp_{v, j, \dots}\) 中的 \(j\) 。故这 \(j\) 个段的贡献为 \(\sum_{v = i}^n 2 j_v (A_v - A_{v + 1})\)(\(j_v\) 表示这 \(j\) 个段到 \(dp_v\) 这一层状态时有多少个连续段),显而易见,\(2 j_v (A_v - A_{v + 1})\) 就不是我们现在该管的,而是应下放到 \(dp_v\) 这一层时处理的,所以本层只需处理 \(A_i - A_{i + 1}\) ,贡献即 \(2(j - 1)(A_i - A_{i + 1}) + p + q\) 。

solution

到这里我们就全部做完啦!但此题传承了插入 dp 一贯的转移方程多,所以要注重一下细节,或者通过循环找内在逻辑来减少手搓的转移方程以简化代码。

/*
address:https://www.becoder.com.cn/problem/4744
AC 2025/8/13 14:06
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 105, V = 1005;
const int mod = 1e9 + 7;
inline void trans(int& x, int y) { x = (x + y) % mod; }
LL dp[N][N][V][2][2];
inline void trans(LL& x, LL y) { x = (x + y) % mod; }
int n, m;
int a[N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1;i <= n;++i) scanf("%d", &a[i]);
if (n == 1) return puts("1"), 0; //注意要特判 n = 1
sort(a + 1, a + n + 1, greater<int>());
dp[1][1][0][1][0] = dp[1][1][0][0][1] = dp[1][1][0][1][1] = 1;
for (int i = 1;i < n;++i)
for (int j = 1;j <= i;++j)
for (int k = 0;k <= m;++k)
for (int p = 0;p < 2;++p)
for (int q = 0;q < 2;++q) {
const int v = k + (a[i] - a[i + 1]) * ((j - 1 << 1) + p + q);
if (v <= m) {
trans(dp[i + 1][j][v][p][q], dp[i][j][k][p][q] * ((j - 1 << 1) + p + q)); //拓展且不改变首尾闭不闭合
trans(dp[i + 1][j + 1][v][p][q], dp[i][j][k][p][q] * (j - 1)); //新建非首尾的段
if (j > 1) trans(dp[i + 1][j - 1][v][p][q], dp[i][j][k][p][q] * (j - 1)); //合并段
if (p) {
trans(dp[i + 1][j][v][!p][q], dp[i][j][k][p][q]); //拓展且闭合头部
trans(dp[i + 1][j + 1][v][p][q], dp[i][j][k][p][q]);
trans(dp[i + 1][j + 1][v][!p][q], dp[i][j][k][p][q]);
//新建在头部
}
if (q) {
trans(dp[i + 1][j][v][p][!q], dp[i][j][k][p][q]);
trans(dp[i + 1][j + 1][v][p][q], dp[i][j][k][p][q]);
trans(dp[i + 1][j + 1][v][p][!q], dp[i][j][k][p][q]);
//同理
}
}
}
LL ans = 0;
for (int i = 0;i <= m;++i) trans(ans, dp[n][1][i][0][0]);
printf("%lld", ans);
return 0;
}

「JOI Open 2016」摩天大楼的更多相关文章

  1. [LOJ#2743][DP]「JOI Open 2016」摩天大楼

    题目传送门 DP 经典题 考虑从小到大把数加入排列内 如下图(\(A\) 已经经过排序): 我们考虑如上,在 \(i\) ( \(A_i\) )不断增大的过程中,维护上面直线 \(y=A_i\) 之下 ...

  2. [题解] [LOJ2743]「JOI Open 2016」摩天大楼

    题目大意 将 \(N\) 个互不相同的整数 \(A_1 , A_2 , ⋯ , A_N\) 任意排列成 \(B_1 , B_2 , ⋯ , B_N\) . 要求 \(∑^{N−1}_{i=1} |B_ ...

  3. LOJ#2351. 「JOI 2018 Final」毒蛇越狱

    LOJ#2351. 「JOI 2018 Final」毒蛇越狱 https://loj.ac/problem/2351 分析: 首先有\(2^{|?|}\)的暴力非常好做. 观察到\(min(|1|,| ...

  4. 「JOI 2017 Final」JOIOI 王国

    「JOI 2017 Final」JOIOI 王国 题目描述 题目译自 JOI 2017 Final T3「 JOIOI 王国 / The Kingdom of JOIOI」 JOIOI 王国是一个 H ...

  5. 【LOJ】#3014. 「JOI 2019 Final」独特的城市(长链剖分)

    LOJ#3014. 「JOI 2019 Final」独特的城市(长链剖分) 显然我们画一条直径,容易发现被统计的只可能是直径某个距离较远的端点到这个点的路径上的值 用一个栈统计可以被统计的点,然后我们 ...

  6. 【题解】LOJ2759. 「JOI 2014 Final」飞天鼠(最短路)

    [题解]LOJ2759. 「JOI 2014 Final」飞天鼠(最短路) 考虑最终答案的构成,一定是由很多飞行+一些上升+一些下降构成. 由于在任何一个点上升或者下降代价是一样的,所以: 对于上升操 ...

  7. 「JOI 2014 Final」飞天鼠

    「JOI 2014 Final」飞天鼠 显然向上爬是没有必要的,除非会下降到地面以下,才提高到刚好为0. 到达一个点有两种情况:到达高度为0和不为0. 对于高度不为0的情况,显然花费的时间越少高度越高 ...

  8. 「JOI 2015 Final」城墙

    「JOI 2015 Final」城墙 复杂度默认\(m=n\) 暴力 对于点\((i,j)\),记录\(ld[i][j]=min(向下延伸的长度,向右延伸的长度)\),\(rd[i][j]=min(向 ...

  9. 「JOI 2015 Final」舞会

    「JOI 2015 Final」舞会 略微思考一下即可知该过程可以化为一棵树.(3个贵族中选择1个,即新建一个节点连向这3个贵族). 该树的结点个数为\(2n\). 考虑二分答案mid. 判定的是公主 ...

  10. 「JOI 2015 Final」分蛋糕 2

    「JOI 2015 Final」分蛋糕 2 题解 这道题让我想起了新年趣事之红包这道DP题,这道题和那道题推出来之后的做法是一样的. 我们可以定义dp[i][len][1] 表示从第i块逆时针数len ...

随机推荐

  1. VSCode将本地项目代码上传到gitee中

    1.创建远程仓库,这个就是该仓库的地址   2.查看git的版本 git --version 3.使用git init命令初始化git 4.使用git status命令来查看文件是否被修改  : gi ...

  2. Java catch多重异常捕获

    摘要:Java中多重异常捕获机制可以更加简洁.有效地处理多个异常,提高了程序的鲁棒性,是编写高质量代码的重要技巧之一.   小编在<浅谈Java异常处理机制>中梳理了异常处理机制,在< ...

  3. synchronized 锁是可重入锁吗?如何验证?

    摘要:举例证明 synchronized锁 是可重入锁,并描述可重入锁的实现原理. 综述   先给大家一个结论:synchronized锁 是可重入锁!   关于什么是可重入锁,通俗来说,当线程请求一 ...

  4. 几种简单的springboot启动后启动一条死循环线程方式

    前言 之前有测试 # 启动类加 @EnableAsync # 方法上加注解 @Async @PostConstruct 但是依旧会卡主主线程,所有另辟蹊径 第一种 在启动类上加注解 @EnableAs ...

  5. 聊聊@Autowired注解的Field injection is not recommended提示问题

    1. 前言 在我接触过的大部分Java项目中,经常看到使用@Autowired注解进行字段注入: import org.springframework.beans.factory.annotation ...

  6. Vue 注意事项

    Top 1 v-once:标签的内容只改变一次: <span v-once>这个将不会改变: {{ msg }}</span> Top 2 v-html:将内容以HTML格式输 ...

  7. ko在数栈中的应用

    ​ 引言 一项技术能得以广泛运用,其中的一个关键点在于工程化.前端从最开始的简单写写网页和样式,发展为需要处理复杂的逻辑,伴随而来的是问题是相关文件越来越多,简单在网页中引用已经解决不了问题,需要相关 ...

  8. 第一次通过 SSH key 免密连接 GitHub 的完整过程

    原文:https://ichochy.com/posts/blog/20221107.html 通过密码连接GitHub总是要输入密码,麻烦,现在使用 SSH key 可以轻松实现免密验证. 创建 S ...

  9. pycharm无法正常调试问题

    pycharm无法正常调试问题 1.错误代码 已连接到 pydev 调试器(内部版本号 231.8109.197)Traceback (most recent call last): File &qu ...

  10. FreeRTOS 学习笔记(持续更新)

    抢占式调度: 高优先级的可以优先运行,即使以及有低优先级的在运行,会先停止低的再运行高的(优先级按数字大小分大小) 高优先级任务不停止,低优先级的任务无法运行 被抢占的任务会进入就绪态 时间片调度: ...