UTS Open 21 P7 - April Fools

news/2025/1/17 21:17:39/文章来源:https://www.cnblogs.com/keysky/p/18677683

传送门

前言

本题是笔者keysky与同学yangbaich讨论+推式子一整个晚上以及讨论前ybc的一整个下午做出来的,综合起来是 \(34\) 个转移方程,对于整道题来说,贡献大抵为我 \(2\)\(8\)
我们的做法不一定是最优解,甚至可以说是较劣且复杂的,但时间是稳定能过且没卡常的,同时对于 \(\text{dp}\) 转移式的意义较为明了,故在此分享一下做法。
此外,特别鸣谢同学XiangXunYi提供的 \(\text{dp}\) 思路。

题意描述

给你一个长度为 \(N\) 的序列 \(A\),让你求出该序列有多少个排列满足对于 \(\forall i \in [1, N - 1]\) ,使得 \(A_i > A_{i + 1}\)\(| \text{MSB}(A_i) -\text{MSB}(A_{i + 1})| = 1\) ,其中 \(\text{MSB}(x)\)\(x\) 在二进制下的位数。
\(N \le 500\)\(A_i \le 10^9\)

思路推导 & 做法

教练讲过,对于计数类的题,不是 \(\text{dp}\) 就是组合计数,对于该题,涉及到二进制下位数的限制和 \(N \le 500\) 的数据范围,能辨别出来是一道 \(\text{dp}\) ,然后根据 \(A_i > A_{i + 1}\) 的条件,容易想到排序(此处笔者是从小到大排,从大到小也行)后按次插入序列,并且采用插入 \(\text{dp}\) 的定义 \(dp_{i, j}\) 表示前 \(i\) 个数有 \(j\) 个连续段,这时就有一个很好的性质:在任意时刻序列都必须合法,因为在排序后,\(\text{MSB}(A_i)\) 一定是单调不减的,对于不合法的 \(A_i < A_{i + 1}\)\(| \text{MSB}(A_i) - \text{MSB}(A_{i + 1})| \neq 1\) ,在定义状态的限制下我们已无法向 \(A_i\)\(A_{i + 1}\) 之间插入元素,也就不可能合法。
现在来思考 \(\text{dp}\) 的定义,首先有 \(2\) 维基础的状态 \(i,j\) 表示插了前 \(i\) 个数,已有 \(j\) 个连续段且考虑相对顺序的方案数,但无论是数据范围还是题目的限制都表明该状态仍有扩展。接下来思考一下新插入一个数有什么限制与性质,这时由 \(A_i < A_{i + 1}\) 可以发现在排序后插入新的数不需要考虑后面一个数,同时前一个数只需要 \(\text{MSB}(A_i) - \text{MSB}(x) = 1\) ,并不需要具体的值,便可以想到扩展 \(2\)\(k, l\) 表示当前有 \(k\) 个连续段以 \(\text{MSB}(A_i) - 1\) 结尾,\(l\) 个连续段以 \(\text{MSB}(A_i)\) 结尾的方案数。
惊世骇俗地发现状态定义就 \(4\) 维了,做是肯定做不了的,所以要优化状态,也就需要挖掘潜在的性质,在深入考虑对于连续段的新建、插入、合并,发现若有超过 \(2\) 个连续段以 \(\le \text{MSB}(A_i) - 2\) 结尾的话,至少有 \(1\) 个位置需要插入数以合并区间,但后面的数的 \(\text{MSB}\) 都比 \(\text{MSB}(A_i)\) 大,连 \(A_i\) 都不合法,比 \(A_i\) 还大的插该位置更是一定不合法,所以同一时刻,最多存在 \(1\) 个连续段以 \(\le \text{MSB}(A_i) - 2\) 结尾且一定是最后一个连续段,所以对于 \(j\) 的一维,我们可以用 \(j, k\) 和结尾区间的结尾的类型来计算,于是 \(\text{dp}\) 的定义就转化为 \(3\) 维 + 常数维:\(dp_{i, j, k, 0/1/2}\) 表示前 \(i\) 个数,有 \(j\) 个连续段以 \(\text{MSB}(A_i) - 1\) 结尾,有 \(k\) 个连续段以 \(\text{MSB}(A_i)\) 结尾,结尾区间的结尾为 \(\text{MSB}(A_i)\) / $\text{MSB}(A_i) - 1 $ / \(\le \text{MSB}(A_i) - 2\) 的方案数。
对于该状态定义,第 \(1\) 维空间可以滚动数组优化掉,所以在空间上没有问题,同时发现 \(3\) 种操作不需要额外枚举,所以时间也是OK的,现在就要考虑杀千刀的转移了。
对于所有转移,我们要考虑插入值的 \(\text{MSB}\) 比当前最后插的值的 \(\text{MSB}\)\(0/1/2\) 的情况,当前状态下有末尾连续段结尾类型为 \(0/1/2\),同时每种情况都有新建\(/\)左扩展\(/\)右扩展\(/\)合并 \(4\) 种方式,同时某些方式又有对插入的位置的 \(2\) 种分类,\(3 \times 3 \times 4 \times 1.5 = 54\) 种转移,当然,实际有很多转移不合法,最终归纳下来是 \(34\) 个转移。

神迹——转移方程

先来简化一下,我们在此省略第一维 \(i\) ,默认从 \(i\)\(i + 1\) 转移,以 \(j, k, 0/1/2\) 表示 \(dp_{i/i + 1, j, k, 0/1/2}\) ,系数直接写在状态后,没有加括号表优先级,见谅。

一定要刷表,一定要刷表,ky我跟你说,用填表你会仙逝。

——ybc(痛苦面具)

对于每一个方程的意义笔者在这就不详细描述了,在明确了状态定义的情况下还是比较浅显的,也留给各位读者去思考。
转移方程×Shit√。

  • \(\text{MSB}(a_{i + 1}) - \text{MSB}(A_i) \ge 2\)
    • 新建连续段
      • \(1, 0, 1 \to 0, 1, 2\)
      • \(0, 1, 0 \to 0, 1, 2\)
    • 扩展连续段左侧
      • \(1, 0, 1 \to 0, 0, 2\)
      • \(0, 1, 0 \to 0, 0, 2\)
  • \(\text{MSB}(a_{i + 1}) - \text{MSB}(A_i) = 1\)
    • 新建连续段
      • \(0, k, 0 \to k, 1, 0\)
      • \(0, k, 0 \times k \to k, 1, 1\)
      • \(1, k, 1 \times (k + 1) \to k, 1, 2\)
      • \(0, k, 2 \times (k + 1) \to k, 1, 2\)
    • 扩展连续段左侧
      • \(0, k, 0 \times k \to k, 0, 1\)
      • \(1, k, 1 \times (k + 1) \to k, 0, 2\)
      • \(0, k, 2 \times (k + 1) \to k, 0, 2\)
    • 扩展连续段右侧
      • \(0, k, 0 \times (k - 1) \to k - 1, 1, 1\)
      • \(0, k, 0 \to k - 1, 1, 0\)
      • \(1, k, 1 \times k \to k - 1, 1, 2\)
      • \(0, k, 2 \times k \to k - 1, 1, 2\)
    • 合并 \(2\) 个连续段
      • \(0, k, 0 \times (k - 1) \to k - 1, 0, 1\)
      • \(1, k ,1 \times k \to k - 1, 0, 2\)
      • \(0, k, 2 \times k \to k - 1, 0, 2\)
  • \(\text{MSB}(a_{i + 1}) - \text{MSB}(A_i) = 0\)
    • 新建连续段
      • \(j, k, 0 \times (j + k + 1) \to j, k + 1, 0\)
      • \(j, k, 1 \times (j + k) \to j, k + 1, 1\)
      • \(j, k, 1 \to j, k + 1, 0\)
      • \(j, k, 2 \times(j + k + 1) \to j, k + 1, 2\)
    • 扩展连续段左侧
      • \(j, k, 0 \times (j + k) \to j, k, 0\)
      • \(j, k, 1 \times (j + k) \to j, k, 1\)
      • \(j, k, 2 \times (j + k + 1) \to j, k, 2\)
    • 扩展连续段右侧
      • \(j, k, 0 \times j \to j - 1, k + 1, 0\)
      • \(j, k, 1 \times (j - 1) \to j - 1, k + 1, 1\)
      • \(j, k, 1 \to j - 1, k + 1, 0\)
      • \(j, k, 2 \times j \to j - 1, k + 1, 2\)
    • 合并 \(2\) 个连续段
      • \(j, k, 0 \times j \to j - 1, k, 0\)
      • \(j, k, 1 \times (j - 1) \to j - 1, k, 1\)
      • \(j, k, 2 \times j \to j - 1, k, 2\)

至此,神迹已成!

solution

代码还是要注意誊抄的时候不要抄错了,不然很难调。

/*
address:http://vjudge.net/problem/DMOJ-utso21p7
AC 2025/1/10 22:06
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 505;
const int mod = 1e9 + 7;
int n;
int a[N], f[N];
int dp[2][N][N][3];
inline void trans(int& x, const int y, const int z) { x = (x + 1ll * y * z % mod) % mod; }
int main() {scanf("%d", &n);for (int i = 1;i <= n;i++) scanf("%d", &a[i]);sort(a + 1, a + n + 1);for (int i = 1;i <= n;i++)for (int j = 31;j >= 0;j--)if (a[i] >> j & 1) {f[i] = j;break;}dp[1][0][1][0] = 1;for (int i = 1;i < n;i++) {for (int j = 0;j <= i + 1;j++)for (int k = 0;k + j <= i + 1;k++)for (int l = 0;l < 3;l++) dp[i + 1 & 1][j][k][l] = 0;for (int j = 0;j <= i;j++)for (int k = 0;k + j <= i;k++) {const int c0 = dp[i & 1][j][k][0], c1 = dp[i & 1][j][k][1], c2 = dp[i & 1][j][k][2];const int  l = (i & 1) ^ 1;if (f[i + 1] - f[i] == 0) {// newtrans(dp[l][j][k + 1][0], c0, j + k + 1);trans(dp[l][j][k + 1][1], c1, j + k);trans(dp[l][j][k + 1][0], c1, 1);trans(dp[l][j][k + 1][2], c2, j + k + 1);// extendtrans(dp[l][j][k][0], c0, j + k);trans(dp[l][j][k][1], c1, j + k);trans(dp[l][j][k][2], c2, j + k + 1);if (j > 0) {trans(dp[l][j - 1][k + 1][0], c0, j);trans(dp[l][j - 1][k + 1][1], c1, j - 1);trans(dp[l][j - 1][k + 1][0], c1, 1);trans(dp[l][j - 1][k + 1][2], c2, j);}// mergeif (j > 0) {trans(dp[l][j - 1][k][0], c0, j);trans(dp[l][j - 1][k][1], c1, j - 1);trans(dp[l][j - 1][k][2], c2, j);}}if (f[i + 1] - f[i] == 1) {// newif (j == 0) trans(dp[l][k][1][0], c0, 1);if (j == 0) trans(dp[l][k][1][1], c0, k);if (j == 1) trans(dp[l][k][1][2], c1, k + 1);if (j == 0) trans(dp[l][k][1][2], c2, k + 1);// extendif (j == 0) trans(dp[l][k][0][1], c0, k);if (j == 0 && k > 0) trans(dp[l][k - 1][1][1], c0, k - 1);if (j == 0 && k > 0) trans(dp[l][k - 1][1][0], c0, 1);if (j == 1) trans(dp[l][k][0][2], c1, k + 1);if (j == 1 && k > 0) trans(dp[l][k - 1][1][2], c1, k);if (j == 0) trans(dp[l][k][0][2], c2, k + 1);if (j == 0 && k > 0) trans(dp[l][k - 1][1][2], c2, k);// mergeif (k > 0) {if (j == 0) trans(dp[l][k - 1][0][1], c0, k - 1);if (j == 1) trans(dp[l][k - 1][0][2], c1, k);if (j == 0) trans(dp[l][k - 1][0][2], c2, k);}}}const int j = i & 1, k = j ^ 1;if (f[i + 1] - f[i] >= 2) {// newtrans(dp[k][0][1][2], dp[j][1][0][1], 1);trans(dp[k][0][1][2], dp[j][0][1][0], 1);trans(dp[k][0][1][2], dp[j][0][0][2], 1);// extendtrans(dp[k][0][0][2], dp[j][1][0][1], 1);trans(dp[k][0][0][2], dp[j][0][1][0], 1);trans(dp[k][0][0][2], dp[j][0][0][2], 1);}}printf("%d\n", ((dp[n & 1][1][0][1] + dp[n & 1][0][1][0]) % mod + dp[n & 1][0][0][2]) % mod);return 0;
}

总结

虽然该题看起来还是插入 \(\text{dp}\) 的套路定义,但状态优化的思维难度是较高的,并且如果没有过人的头脑就只能像笔者一样写出这一坨转移方程,在原OJ上有一些大佬通过一些笔者看不懂的循环让转移方程只剩七八个甚至五个。
说实话,打完此题确实是神清气爽,以后吹牛就可以说:“你见过 \(34\) 个状态转移方程的 \(\text{dp}\) 吗?”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/870965.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL架构】图解

你是一个程序员,你做了一个网站应用,站点里的用户数据,需要存到某个地方,方便随时读写。 很容易想到可以将数据存到文件里。 但如果数据量很大,想从大量文件数据中查找某部分数据,并更新,是一件很痛苦的事情。 那么问题就来了,有办法可以解决这个问题吗? 好办,没有什…

2025 郑州一测 T18: 双变量问题探讨

2025 高考加把劲 /qtContent已知函数 \(f(x) = \log_a x(a>0, a\neq 1)\), \(y = f(x)\) 关于 \(y=x\) 对称的函数记为 \(g(x)\). (I) 若 \(a>1\), 方程 \(f(x)-g(x)=0\) 有且仅有一个实数解, 求 \(a\) 的值. (II) 讨论方程 \(g(x) = x_a = 0\) 在 \((0, +\infty)\) 上实…

笑死

哈哈哈哈哈笑死这个中国银行的标志出现的恰到好处,我还以为是什么标识

C#数据结构与算法入门实战指南

前言 在编程领域,数据结构与算法是构建高效、可靠和可扩展软件系统的基石。它们对于提升程序性能、优化资源利用以及解决复杂问题具有至关重要的作用。今天大姚分享一些非常不错的C#数据结构与算法实战教程,希望可以帮助到有需要的小伙伴。 C#经典十大排序算法 主要讲解C#经典…

29. 数据库操作

一、SQL与数据库数据库 (database)是统一管理的、有组织的、可共享的大量数据的集合。数据库将数据存储在一个或多个表格中,管理这个数据库的软件称为 数据库管理系统(database management system, DBMS)。数据库不是针对具体的应用程序,而是立足于数据本身的管理,它将所…

用Mermaid画图

1、用Mermaid画图 mermaid.md TyporaPortable.rar mermaid.zip 目录1 Mermaid是什么1.1 概述1.2 网址官网地址:Github地址:图形图形几种图形名字节点与无名字节点设置样式:style, classDef, class, :::线条图形连线(--)及注释(%%)线条样式实线与虚线箭头实线与粗实线及箭头延…

【RabbitMQ】图解

你是一个程序员,假设你维护了两个服务 A 和 B。 A 服务负责转发用户请求到 B 服务,B 服务是个算法服务,GPU 资源有限,当请求量大到 B 服务处理不过来的时候,希望能优先处理会员用户的请求。 那么问题就来了,如果普通用户和会员用户同时发起请求,怎样才能做到会员优先呢?…

THREE.js学习笔记8——Textures

这个小节主要学习纹理,Texture 纹理是覆盖几何形状表面的图像,不同类型的纹理具有多种不同的效果。 这些纹理(尤其是金属性和粗糙度)遵循PBR原则基于物理的渲染 许多技术往往遵循现实生活中的方向以获得现实的结果 成为现实渲染的标准 许多软件、引擎和库都在使用它如何加载…

快速傅里叶变换总结

基本概念 对于求和式 \(\sum a_ix^i\),如果是有限项相加,称为多项式,记作 \[f(x)=\sum_{i=0}^n a_ix^i。 \]其中最高次项的次数为 \(n\),为 \(n\) 次多项式。 用 \(n+1\) 个点可以唯一地确定一个 \(n\) 次多项式,这一过程可以参考 拉格朗日插值。 引入 给定多项式 \(f(x),…

寒假学习1

老年人评估系统 初步整理web端思路先写了第一张信息表并搭建基本框架并编写了老年人信息添加功能

1.17安卓测试

今天在idea进行安卓虚拟机测试成功,昨天的错误是因为版本不兼容 启动虚拟机运行测试

【Azure APIM】升级中国区APIM服务 stv1 到 stv2 遇见错误

Invalid parameter: This Migration API option is not supported or is temporarily disabled due to internal issues. Please visit https://aka.ms/apim-migrate-stv2 to see other migration options.问题描述 在执行Azure API Management服务升级的操作中 (从 stv1 升级到…