Acwing2554. 排列数

2023-11-18

在一个排列中,一个折点是指排列中的一个元素,它同时小于两边的元素,或者同时大于两边的元素。

对于一个 1∼n 的排列,如果可以将这个排列中包含 t 个折点,则它称为一个 t+1 单调序列。

例如,排列 (1,4,2,3) 是一个 3 单调序列,其中 4 和 2 都是折点。

给定 n 和 k请问 1∼n 的所有排列中有多少个 k 单调队列?

输入格式

输入一行包含两个整数 n,k。

输出格式

输出一个整数,表示答案。

答案可能很大,你可需要输出满足条件的排列数量除以 123456 的余数即可。

数据范围

1≤k≤n≤500

输入样例:

4 2

输出样例:

12
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 520;
const int mod = 123456;

int n, k;
int f[N][N];

int main() //动态规划
{
    cin >> n >> k;

    f[1][1] = 1;
    f[2][1] = 2;

    for (int i = 3; i <= n; i++)
        for (int j = 1; j <= k && j <= i; j ++)
        {
            (f[i][j] += f[i - 1][j] * j) %= mod;
            (f[i][j] += f[i - 1][j - 1] * 2) %= mod;
            if (j > 1) (f[i][j] += f[i - 1][j - 2] * (i - j)) %= mod;
        }

    cout << f[n][k] << endl;

    return 0;
}


这里先定义几个名词:

在一个排列中,一个山峰是指排列中的一个元素同时大于两边的元素。

在一个排列中,一个山谷是指排列中的一个元素同时小于两边的元素。

那么题目中的折点数量即为排列中山谷数量和山峰数量的和。

算法
(DP) O(nk)
考虑从 1 到 n 填整个排列。

记 f[i][j] 表示填了前i 个数,且填出来的排列为 “j 单调序列” 的方案数。

接下来考虑状态转移。

因为我们是从 1 到 n 逐个填的,所以我们只需考虑从 i−1 转移到 i 的情况即可,这大大降低了我们考虑的范围。

即使如此,这个状态转移依然比较难想,要先注意到下面这个性质:

将 i 填到 1∼i−1 的一个排列中,至多会多出两个折点(折点定义见题目描述)

这个性质不难理解,可以拿张纸随便画画。严格证明 其实一点都不严格 如下:

证明:

因为我们是将 i 填到 1∼i−1 的一个排列中,那么新填进去的 i 必然是排列中最大的数。

那么因为是最大的数,放到排列中之后,如果不放到最左/最右端,则必然成为一个山峰。

因为我们要放完之后的折点数量更多,所以我们假设不将其放到最左/最右端。

我们可以分以下三种情况分类讨论:

如果我们填到原排列中某个山峰的旁边:
假设我们放到了某个下标为 x 的山峰的左边,那么对于 xx 来说,它就不比它左边更大了,也就不再是山峰了。
对于 ii 的左边,因为原来它的右边是山峰,所以它必然比它右边小,放入 ii 之后它依然比右边小,所以它原来是啥现在就是啥。
而新放进去的 ii 则会成为一个山峰,那么我们相当于少了一个山峰后,又多了一个山峰,所以总的折点数量不变。
放到山峰右边同理。
如果我们填到原序列中的山谷的旁边:
假设我们放到了某个下标为 xx 的山谷的左边,那么对于 xx 来说,它依然是一个山谷。
对于新放进去的 ii 来说,它会成为一个山峰。
对于 ii 的左边来说,原来它的右边是山谷,所以必然比它小,而放入 ii 之后,它的右边就会比它大。
那我们就需要对它的左边考虑两种情况:
它的左边比它大
放入 ii 后,它的左边和右边都比自己大,则它会成为一个山谷。
它的左边比它小
放入 ii 后,它左边比自己小,右边比自己大,则它既不是山谷也不是山峰。
那么我们最多就会增加一个山峰和一个山谷,所以总的折点数量最多增加 22
放到山谷右边同理。
其它情况:
对于其它情况,假设我们放到排列中的 i 下标为 x,放入 i 后的排列为 p。假设 p[x−1]<p[x+1]。
放入 i 之后,考虑其左边的 p[x−1],不论是否放入 i ,它右边都比自己大,所以放入 i 不对其构成影响。
考虑其右边的 p[x+1],放入 i 后,它的左边比自己大了,那么我们需要对它右边考虑两种情况:
它的右边比它大
放入 i 后,它的左边和右边都比自己大,则它会成为一个山谷。
它的右边比它小
放入 i 后,它的左边比自己大,右边比自己小,则它既不是山峰也不是山谷。
那么我们最多会增加一个山峰和一个山谷,所以总的折点数量最多增加 2
综上所述,总的折点数量最多增加 2。

那么有了这个性质之后,可推出状态转移方程的“轮廓”大致如下:

f[i][j] = f[i - 1][j] * x + f[i - 1][j - 1] * y + f[i - 1][j - 2] * z
那么接下来的问题,就是解决 x,y,z 分别是什么。

首先算 xx,这里先直接给出结论:当 i>2 时,x=j,即 放入i后,使排列折点不增加的数量放入i后,使排列折点不增加的数量,就是 原排列中折点数量+1原排列中折点数量+1。

证明:

考虑如何放置 i,使折点数量不增加。

上面的证明过程中讨论过,如果我们将 ii 放到了原来某个山峰的旁边,则折点数量不增加。

但是,我们讨论的情况中,没有涉及到将 ii 放到边界的情况。

假设我们将 i 放到了排列的最左端,设原排列中最左端的元素为 xx。

因为 i 放到了排列最左端,所以 i 并不会成为山峰。

如果 x 右边的元素比 x 小,则放入i 后,x 既不是山峰也不是山谷,那么排列中总的折点数量就不会改变。

如果 x 右边的元素比 x 大,则放入 i 后,x 会成为一个山谷,那么排列中总的折点数量会增加 11。

那么如果将 i 放入最左端后,原排列中最左端两个元素递减,则排列中总的折点数量就不会改变。

同样,如果放入最右端,原排列中最右端两个元素递增,则排列中总的折点数量就不会改变。

那么就有 x=原排列中山峰个数×2+[原排列最左端两个元素递减]+[原排列最右端两个元素递增]
(那个 ×2 是因为我们可以将 i 放入山峰的左边,也可以放入其右边)

而我们的山峰和山谷必然是交错分布的,所以山峰个数和山谷个数的差必然不超过 1。

如果原排列中,山峰数量=山谷数量−1,则第一个折点和最后一个折点就都是山谷,那么最左端两个元素必然递减,最右端两个元素递增。(这里如果理解不了,可以画个柱状图)

那么就有 原排列中山峰个数×2+[原排列最左端两个元素递减]+[原排列最右端两个元素递增]=(山峰个数)+(山谷数量−1)+1+1=折点数量+1
如果 山峰数量=山谷数量,则要么是第一个折点是山峰、最后一个折点山谷,要么是第一个折点是山谷,最后一个折点是山峰,那么 [原排列最左端两个元素递减] 和 [原排列最右端两个元素递增] 必然一个为真一个为假。

那么就有 原排列中山峰个数×2+[原排列最左端两个元素递减]+[原排列最右端两个元素递增]=山峰个数+山谷数量+1=折点数量+1
如果 山峰数量=山谷数量+1,则第一个折点和最后一个折点就都是山峰,那么最左端两个元素必然递增,最右端两个元素递减。

那么就有 原排列中山峰个数×2+[原排列最左端两个元素递减]+[原排列最右端两个元素递增]=山峰个数+(山谷数量+1)+0折点数量+1
综上所述,放入i后,使排列折点不增加的数量=原排列中折点数量+1放入i后,使排列折点不增加的数量=原排列中折点数量+1,即 x=j
然后算 y,这里同样直接给出结论:当 i>2 时,y=2
证明:

根据上面性质中的证明过程,并没有发现有放完 i 之后折点数量增加 1 的情况。

所以想要放入 i 后,折点数量增加 1,必然是放在端点附近。

假设我们将 i 放入原排列左端点附近。

那么如果原排列最左端两个元素递减,则将 i 放到第一个元素后面时,排列中折点数量会增加 1。

如果原排列最左端两个元素递增,则将 i 放到第一个元素前时,排列中折点数量会增加 1。

所以不论什么情况,必然存在一种将 i 放到左端点的旁边的方案,使得排列中折点数量增加1。

右端点同理。

所以 i>2 时,放入放入i后,使排列中折点数量增加后,使排列中折点数量增加1的数量=2
最后,计算 z。

这个相对好算一些,考虑我们放入 i 之前,排列中共有 i−1个数,且该排列为 j−2 单调序列。

那么根据上面的一顿分析,我们知道将 i 放入这个序列后,有 j−2 种放置方式使该排列折点数不变,有 2 中放置方式使该排列中折点数加一。

而将 i 放入这 i−1 个数中,总共有 i 种放法。

所以放入之后使其折点数量 +2 的放法有 i−(j−2)−2=i−j 种。

故 z=(i−j)
那么我们就可以得到转移方程了:

f[i][j] = f[i - 1][j] * j + f[i - 1][j - 1] * 2 + f[i - 1][j - 2] * (i - j)
最后考虑初始化。

我们上面求得的 x,y,z,是有一定限制的,即 i>2,所以我们需要将 i=1 和 i=2 的情况都初始化出来。

对于 i=1 的情况,排列中不可能有折点,故排列必然为 1 单调序列,排列只有一种 f[1][1]=1。

对于 i=2 的情况,排列中不可能有折点,故排列必然为 1 单调序列,排列有两种 f[2][1]=2

终于写完了

时间复杂度
DP时间复杂度 = 转移复杂度 × 状态数量

本题中转移复杂度为 O(1),状态数量为 n×k,故时间复杂度为 O(nk)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Acwing2554. 排列数 的相关文章

  • 编译时运算符

    有人可以列出 C 中可用的所有编译时运算符吗 C 中有两个运算符 无论操作数如何 它们的结果始终可以在编译时确定 它们是sizeof 1 and 2 当然 其他运算符的许多特殊用途可以在编译时解决 例如标准中列出的那些整数常量表达式 1 与
  • 如何使用 C# 中的参数将用户重定向到 paypal

    如果我有像下面这样的简单表格 我可以用它来将用户重定向到 PayPal 以完成付款
  • 没有强命名的代码签名是否会让您的应用程序容易被滥用?

    尝试了解authenticode代码签名和强命名 我是否正确地认为 如果我对引用一些 dll 非强命名 的 exe 进行代码签名 恶意用户就可以替换我的 DLL 并以看似由我签名但正在运行的方式分发应用程序他们的代码 假设这是真的 那么您似
  • 通过 CMIS (dotCMIS) 连接到 SP2010:异常未经授权

    我正在使用 dotCMIS 并且想要简单连接到我的 SP2010 服务器 我尝试用 C 来做到这一点 如下所示http chemistry apache org dotnet getting started with dotcmis htm
  • “构建”构建我的项目,“构建解决方案”则不构建

    我刚刚开始使用VS2010 我有一个较大的解决方案 已从 VS2008 成功迁移 我已将一个名为 Test 的控制台应用程序项目添加到解决方案中 选择构建 gt 构建解决方案不编译新项目 选择构建 gt 构建测试确实构建了项目 在失败的情况
  • Web 客户端和 Expect100Continue

    使用 WebClient C NET 时设置 Expect100Continue 的最佳方法是什么 我有下面的代码 我仍然在标题中看到 100 continue 愚蠢的 apache 仍然抱怨 505 错误 string url http
  • WCF 中 SOAP 消息的数字签名

    我在 4 0 中有一个 WCF 服务 我需要向 SOAP 响应添加数字签名 我不太确定实际上应该如何完成 我相信响应应该类似于下面的链接中显示的内容 https spaces internet2 edu display ISWG Signe
  • 使用 Bearer Token 访问 IdentityServer4 上受保护的 API

    我试图寻找此问题的解决方案 但尚未找到正确的搜索文本 我的问题是 如何配置我的 IdentityServer 以便它也可以接受 授权带有 BearerTokens 的 Api 请求 我已经配置并运行了 IdentityServer4 我还在
  • SolrNet连接说明

    为什么 SolrNet 连接的容器保持静态 这是一个非常大的错误 因为当我们在应用程序中向应用程序发送异步请求时 SolrNet 会表现异常 在 SolrNet 中如何避免这个问题 class P static void M string
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • Windows 窗体:如果文本太长,请添加新行到标签

    我正在使用 C 有时 从网络服务返回的文本 我在标签中显示 太长 并且会在表单边缘被截断 如果标签不适合表单 是否有一种简单的方法可以在标签中添加换行符 Thanks 如果您将标签设置为autosize 它会随着您输入的任何文本自动增长 为
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • 对现有视频添加水印

    我正在寻找一种用 C 在视频上加水印的方法 就像在上面写文字一样 图片或文字标签 我该怎么做 谢谢 您可以使用 Nreco 视频转换器 代码看起来像 NReco VideoConverter FFMpegConverter wrap new
  • 如何从两个不同的项目中获取文件夹的相对路径

    我有两个项目和一个共享库 用于从此文件夹加载图像 C MainProject Project1 Images 项目1的文件夹 C MainProject Project1 Files Bin x86 Debug 其中有project1 ex
  • 如何将带有 IP 地址的连接字符串放入 web.config 文件中?

    我们当前在 web config 文件中使用以下连接字符串 add name DBConnectionString connectionString Data Source ourServer Initial Catalog ourDB P
  • C# 模拟VolumeMute按下

    我得到以下代码来模拟音量静音按键 DllImport coredll dll SetLastError true static extern void keybd event byte bVk byte bScan int dwFlags
  • 如何将服务器服务连接到 Dynamics Online

    我正在修改内部管理应用程序以连接到我们的在线托管 Dynamics 2016 实例 根据一些在线教程 我一直在使用OrganizationServiceProxy out of Microsoft Xrm Sdk Client来自 SDK
  • C# - OutOfMemoryException 在 JSON 文件上保存列表

    我正在尝试保存压力图的流数据 基本上我有一个压力矩阵定义为 double pressureMatrix new double e Data GetLength 0 e Data GetLength 1 基本上 我得到了其中之一pressur
  • 如何防止用户控件表单在 C# 中处理键盘输入(箭头键)

    我的用户控件包含其他可以选择的控件 我想实现使用箭头键导航子控件的方法 问题是家长控制拦截箭头键并使用它来滚动其视图什么是我想避免的事情 我想自己解决控制内容的导航问题 我如何控制由箭头键引起的标准行为 提前致谢 MTH 这通常是通过重写
  • 对来自流读取器的过滤数据执行小计

    编辑问题未得到解答 我有一个基于 1 个标准的过滤输出 前 3 个数字是 110 210 或 310 给出 3 个不同的组 从流阅读器控制台 问题已编辑 因为第一个答案是我给出的具体示例的字面解决方案 我使用的实际字符串长度为 450 个

随机推荐