UTS Open '21 P7 - April Fools
前言
本题是笔者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) {
// new
trans(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);
// extend
trans(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);
}
// merge
if (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) {
// new
if (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);
// extend
if (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);
// merge
if (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) {
// new
trans(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);
// extend
trans(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}\) 吗?”。
UTS Open '21 P7 - April Fools的更多相关文章
- April Fools Day Contest 2014
April Fools Day Contest 2014 A.C.H三道题目 ============================================================= ...
- 坑爹CF April Fools Day Contest题解
H - A + B Strikes Back A + B is often used as an example of the easiest problem possible to show som ...
- April Fools Day Contest 2014 H. A + B Strikes Back
H. A + B Strikes Back time limit per test 1 second memory limit per test 256 megabytes input standar ...
- April Fools Day Contest 2016 F. Ace It!
F. Ace It! 题目连接: http://www.codeforces.com/contest/656/problem/F Description Input The only line of ...
- CF #April Fools Day Contest 2016 E Out of Controls
题目连接:http://codeforces.com/problemset/problem/656/E 愚人节专场的E,整个其实就是个Floyd算法,但是要求代码中不能包含 definedoforfo ...
- April Fools Contest 2017 题解&源码(A,数学 B,数学 C,数学 D,字符串 E,数字逻辑 F,排序,卡时间,G,数学)
A. Numbers Joke time limit per test:2 seconds memory limit per test:64 megabytes input:standard inpu ...
- Codeforces April Fools Contest 2017
都是神题,我一题都不会,全程听学长题解打代码,我代码巨丑就不贴了 题解见巨神博客 假装自己没有做过这套
- April Fools Contest 2017 题解
趁着上课无聊,来补一补-- A. Numbers Joke 直接oeis就好了:http://oeis.org/search?q=numbers+joke&language=english&a ...
- April Fools Day Contest 2016 D. Rosetta Problem
D. Rosetta Problem 题目连接: http://www.codeforces.com/contest/656/problem/D Description ++++++++[>+& ...
- April Fools Day Contest 2016 G. You're a Professional
G. You're a Professional 题目连接: http://www.codeforces.com/contest/656/problem/G Description A simple ...
随机推荐
- win11 与VMware pro16不兼容或者是不能嵌套虚拟或者此平台不支持虚拟化的Intel VT-x/EPT等问题
如遇 不用去掉啥windows沙盒,不用关掉什么Hyper-V. 可以在window11的应用里面的可选功能把虚拟平台的勾去掉然后重启一下 而虚拟机不在配置处理器的时候不勾选下图这个 就会出现这种情况 ...
- (Python基础教程之二)在Sublime Editor中配置Python环境
Python基础教程 在SublimeEditor中配置Python环境 Python代码中添加注释 Python中的变量的使用 Python中的数据类型 Python中的关键字 Python字符串操 ...
- 分布式配置中心之Apollo
Apollo(阿波罗)是携程开源的一款可靠的分布式配置管理中心,它能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理场 ...
- Phpstorm之快捷键
常用快捷键 1.ctrl+alt+s 快速打开setting系统设置 2.CTRL+/ 单行注释/取消注释 CTRL+SHIFT+/ 块状注释/取消块状注释 3.方法体上面打入'/**' 再按回车键 ...
- 设计模式:可复用面向对象软件的基础 pdf电子书分享
<设计模式:可复用面向对象软件的基础>是引导读者走出软件设计迷宫的指路明灯,凝聚了软件开发界几十年设计经验的结晶.四位顶尖的面向对象领域专家精心选取了最具价值的设计实践,加以分类整理和命名 ...
- uni-app小程序(快手)日志打印坑位记录
前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE让开发体验也挺棒的,公司项目就是主推uni-app. 坑位 最近在开发一 ...
- IO介绍-下
中断 由外部设备引起的中断,称为外中断. 由内部错误引起的中断,称为内中断,或者是陷入.例如:非法指令,地址越界,电源故障等. 中断向量表 中断优先级 多中断源的处理方式 屏蔽中断 嵌套中断 根据 ...
- openEuler欧拉使用rc.local实现开机自启动
设置权限 chmod 775 /etc/rc.local 普通的单条是,直接写在rc.local里 /usr/local/nacos/bin/startup.sh -m standalone 复杂点 ...
- zz 失血模型与充血模型等
失血模型与充血模型 | 三秋 (贫血模型)优点是系统的层次结构清楚,各层之间单向依赖,Client->(BusinessFacade)->BusinessLogic->Data Ac ...
- 【数据库】MySQL概念性基础知识期末复习
选择题 第一章 3 二维表结构--数据模型--关系数据模型 5 描述全部数据整体逻辑结构--模式 6 逻辑数据独立性--模式变,外模式和应用程序不变 7 物理数据独立性--内模式变,外模式和应用程序不 ...