云计算百科
云计算领域专业知识百科平台

P8699 [蓝桥杯 2019 国 B] 排列数|普及+

本文涉及知识点

数论:质数、最大公约数、菲蜀定理

P8699 [蓝桥杯 2019 国 B] 排列数

题目描述

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

1

n

1 ∼ n

1n 的排列,如果可以将这个排列中包含

t

t

t 个折点,则它称为一个

t

+

1

t + 1

t+1 单调排列。 例如,排列

(

1

,

4

,

2

,

3

)

(1, 4, 2, 3)

(1,4,2,3) 是一个

3

3

3 单调排列,其中

4

4

4

2

2

2 都是折点。 给定

n

n

n

k

k

k,请问

1

n

1 ∼ n

1n 的所有排列中有多少个

k

k

k 单调排列?

输入格式

输入一行包含两个整数

n

n

n,

k

k

k

输出格式

输出一个整数,表示答案。答案可能很大,你可需要输出满足条件的排列 数量除以

123456

123456

123456 的余数即可。

输入输出样例 #1

输入 #1

4 2

输出 #1

12

说明/提示

对于

20

%

20 \\%

20% 的评测用例,

1

k

n

10

1 \\leq k \\leq n \\leq 10

1kn10;

对于

40

%

40 \\%

40% 的评测用例,

1

k

n

20

1 \\leq k \\leq n \\leq 20

1kn20; 对于

60

%

60 \\%

60% 的评测用例,

1

k

n

100

1 \\leq k \\leq n \\leq 100

1kn100;

对于所有评测用例,

1

k

n

500

1 \\leq k \\leq n \\leq 500

1kn500

蓝桥杯 2019 年国赛 B 组 G 题。

数论

通过N排列构建N+1排列

f(a,n)=b 。a是长度为N的排列(以下简称N排列),b是长度为N+1的排列,

n

[

0

,

N

]

n\\in[0,N]

n[0,N]表示将N+1插入到n处。下面来证明f是单射函数。 一,

n

1

n

2

,则

f

(

a

1

,

n

1

)

f

(

a

2

,

n

2

)

n1 \\neq n2,则f(a1,n1)\\neq f(a2,n2)

n1=n2,则f(a1,n1)=f(a2,n2)。因为

b

1

[

n

1

]

b

2

[

n

2

]

b1[n1]\\neq b2[n2]

b1[n1]=b2[n2]。 二,

n

1

=

=

n

2

,

a

1

a

2

一定存在

a

1

[

i

1

]

a

2

[

i

1

]

如果

n

1

>

i

1

,则

b

1

[

i

1

]

b

1

[

i

1

]

;否则

b

1

[

i

1

+

1

]

b

1

[

i

1

+

1

]

n1 == n2,a1 \\neq a2 \\rightarrow 一定存在a1[i1] \\neq a2[i1] 如果n1 > i1,则b1[i1] \\neq b1[i1];否则b1[i1+1] \\neq b1[i1+1]

n1==n2,a1=a2一定存在a1[i1]=a2[i1]如果n1>i1,则b1[i1]=b1[i1];否则b1[i1+1]=b1[i1+1]

单调区间

一个排列有k个折点,下标分别为

i

1

,

i

2

i

k

1

,

i

k

i_1,i_2\\cdots i_k-1,i_k

i1,i2ik1,ik 则只有两种情况。情况一:

a

[

0…

i

1

]

a

[

i

2

.

.

.

i

3

]

a

[

i

4

.

.

.

i

5

]

a[0…i_1] a[i_2…i_3] a[i_4…i_5]\\cdots

a[0…i1]a[i2i3]a[i4i5] 单调递增,其它单调递减。 情况二:

a

[

0…

i

1

]

a

[

i

2

.

.

.

i

3

]

a

[

i

4

.

.

.

i

5

]

a[0…i_1] a[i_2…i_3] a[i_4…i_5]\\cdots

a[0…i1]a[i2i3]a[i4i5] 单调递减,其它单调递增。 总结一:共k+1个单调区间,奇偶性相同的区间单调性相同;奇偶性不同的区间单调性不同。 性质一:任意排列c[i]=N+1-a[i] (第k大的数变低k小),则a、c的单调区间数量相等,单调性相反。我们成ac为一对。 如果a[i] < a[i+1],则是增加一条右上的线,否则右下的线。则对应以下两种情况: 在这里插入图片描述 dp[n][k]n排列共有k个折点的方案数。 情况一:对于任意a,n = N。如果a[N-1] < a[N-2],则b比a多一个折点N-1;否则折点不变。 情况二:n==0,如果a[0] < a[1],折点+1,否则折点不变。 总结二:对每对方案,情况一情况二折点不变和折点加+1的方案数一样。即: dp[n+1][k] += dp[n][k] dp[n+1][k+1] += dp[n][k] 情况三:除去情况一二,我们可以看成边上加一点。令在边i,i+1间插入N+1。 mi = min(a[i],a[i+1]) ma = max(a[i],a[i+1])。 由于mi小于ma,故mi不会是大折点。 如果mi是小折点,则N+1代替a[ma]后,仍然是小折点。故无需讨论mi。 如果ma是大折点,则插入后。 ma不再是大折点。N+1变成大折点。一对方案,共K个大折点,共2k条边。故:dp[n][k] += dp[n][k] *k。 如果ma是a[0]或a[N-1],插入后,ma不是折点。N+1是折点,故折点+1。dp[n+1][k+1] += dp[n][k] 如果ma不是a[0]和a[N-1],插入后。ma和N+1都是折点,折点+2。 总结三:f函数执行后,折点可能不变,+1,+2。 总方案数:n+1 折点不变的方案数:1+k 折点+1的方案数:2 折点+2的方案数:

(

n

+

1

)

(

1

+

k

)

2

=

n

k

2

(n+1)-(1+k)-2=n-k-2

(n+1)(1+k)2=nk2

代码

核心代码

#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include<array>

#include <bitset>
using namespace std;

template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
in >> pr.first >> pr.second;
return in;
}

template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t);
return in;
}

template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
return in;
}

template<class T = int>
vector<T> Read() {
int n;
cin >> n;
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
vector<T> ret;
T tmp;
while (cin >> tmp) {
ret.emplace_back(tmp);
if ('\\n' == cin.get()) { break; }
}
return ret;
}

template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}

template<int N = 1'000'000>
class COutBuff
{
public:
COutBuff() {
m_p = puffer;
}
template<class T>
void write(T x) {
int num[28], sp = 0;
if (x < 0)
*m_p++ = '-', x = x;

if (!x)
*m_p++ = 48;

while (x)
num[++sp] = x % 10, x /= 10;

while (sp)
*m_p++ = num[sp] + 48;
AuotToFile();
}
void writestr(const char* sz) {
strcpy(m_p, sz);
m_p += strlen(sz);
AuotToFile();
}
inline void write(char ch)
{
*m_p++ = ch;
AuotToFile();
}
inline void ToFile() {
fwrite(puffer, 1, m_p puffer, stdout);
m_p = puffer;
}
~COutBuff() {
ToFile();
}
private:
inline void AuotToFile() {
if (m_p puffer > N 100) {
ToFile();
}
}
char puffer[N], * m_p;
};

template<int N = 1'000'000>
class CInBuff
{
public:
inline CInBuff() {}
inline CInBuff<N>& operator>>(char& ch) {
FileToBuf();
while (('\\r' == *S) || ('\\n' == *S) || (' ' == *S)) { S++; }//忽略空格和回车
ch = *S++;
return *this;
}
inline CInBuff<N>& operator>>(int& val) {
FileToBuf();
int x(0), f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? x : x; S++;//忽略空格换行
return *this;
}
inline CInBuff& operator>>(long long& val) {
FileToBuf();
long long x(0); int f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? x : x; S++;//忽略空格换行
return *this;
}
template<class T1, class T2>
inline CInBuff& operator>>(pair<T1, T2>& val) {
*this >> val.first >> val.second;
return *this;
}
template<class T1, class T2, class T3>
inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val);
return *this;
}
template<class T1, class T2, class T3, class T4>
inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);
return *this;
}
template<class T = int>
inline CInBuff& operator>>(vector<T>& val) {
int n;
*this >> n;
val.resize(n);
for (int i = 0; i < n; i++) {
*this >> val[i];
}
return *this;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
*this >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read() {
vector<T> ret;
*this >> ret;
return ret;
}
private:
inline void FileToBuf() {
const int canRead = m_iWritePos (S buffer);
if (canRead >= 100) { return; }
if (m_bFinish) { return; }
for (int i = 0; i < canRead; i++)
{
buffer[i] = S[i];//memcpy出错
}
m_iWritePos = canRead;
buffer[m_iWritePos] = 0;
S = buffer;
int readCnt = fread(buffer + m_iWritePos, 1, N m_iWritePos, stdin);
if (readCnt <= 0) { m_bFinish = true; return; }
m_iWritePos += readCnt;
buffer[m_iWritePos] = 0;
S = buffer;
}
int m_iWritePos = 0; bool m_bFinish = false;
char buffer[N + 10], * S = buffer;
};

template<long long MOD = 1000000007, class T1 = int, class T2 = long long>
class C1097Int
{
public:
C1097Int(T1 iData = 0) :m_iData(iData% MOD)
{

}
C1097Int(T2 llData) :m_iData(llData% MOD) {

}
C1097Int operator+(const C1097Int& o)const
{
return C1097Int(((T2)m_iData + o.m_iData) % MOD);
}
C1097Int& operator+=(const C1097Int& o)
{
m_iData = ((T2)m_iData + o.m_iData) % MOD;
return *this;
}
C1097Int& operator-=(const C1097Int& o)
{
m_iData = ((T2)MOD + m_iData o.m_iData) % MOD;
return *this;
}
C1097Int operator(const C1097Int& o)const
{
return C1097Int(((T2)MOD + m_iData o.m_iData) % MOD);
}
C1097Int operator*(const C1097Int& o)const
{
return((T2)m_iData * o.m_iData) % MOD;
}
C1097Int& operator*=(const C1097Int& o)
{
m_iData = ((T2)m_iData * o.m_iData) % MOD;
return *this;
}
C1097Int operator/(const C1097Int& o)const
{
return *this * o.PowNegative1();
}
C1097Int& operator/=(const C1097Int& o)
{
*this *= o.PowNegative1();
return *this;
}
bool operator==(const C1097Int& o)const
{
return m_iData == o.m_iData;
}
bool operator<(const C1097Int& o)const
{
return m_iData < o.m_iData;
}
C1097Int pow(T2 n)const
{
C1097Int iRet = (T1)1, iCur = *this;
while (n)
{
if (n & 1)
{
iRet *= iCur;
}
iCur *= iCur;
n >>= 1;
}
return iRet;
}
C1097Int PowNegative1()const
{
return pow(MOD 2);
}
T1 ToInt()const
{
return ((T2)m_iData + MOD) % MOD;
}
private:
T1 m_iData = 0;;
};

class Solution {
public:
int Ans(const int N, const int K) {
if (1 == N) { return 1 == K; }
typedef C1097Int<123456> BI;
vector<BI> pre = { 2,0,0 };
for (int n = 2; n < N; n++) {
vector<BI> cur(n + 2);
for (int k = 0; k < pre.size(); k++) {
cur[k] += pre[k] * (1 + k);
cur[k + 1] += pre[k] * 2;
if (n k 2 > 0)
{
cur[k + 2] += pre[k] * (n k 2);
}
}
pre.swap(cur);
}
return pre[K 1].ToInt();
}

};

int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
ios::sync_with_stdio(0); cin.tie(nullptr);
//CInBuff<> in; COutBuff<10'000'000> ob;

int N,K;
cin >> N >> K;
#ifdef _DEBUG
//printf("N=%d,K=%d", N,M);
//Out(grid, ",gird=");
//Out(rr, ",rr=");
//Out(ab, ",ab=");
//Out(par, "par=");
//Out(que, "que=");
//Out(B, "B=");
#endif // DEBUG
auto res = Solution().Ans(N,K);
cout << res << "\\n";
return 0;
};

单元测试

TEST_METHOD(TestMethod11)
{
auto res = Solution().Ans(4,2);
AssertEx(12, res);
}
TEST_METHOD(TestMethod12)
{
auto res = Solution().Ans(2, 1);
AssertEx(2, res);
}
TEST_METHOD(TestMethod13)
{
auto res = Solution().Ans(1, 1);
AssertEx(1, res);
}
TEST_METHOD(TestMethod14)
{
auto res = Solution().Ans(1, 2);
AssertEx(0, res);
}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。 https://edu.csdn.net/course/detail/38771 如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程 https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17 或者 操作系统:win10 开发环境: VS2022 C++17 如无特殊说明,本算法用**C++**实现。

赞(0)
未经允许不得转载:网硕互联帮助中心 » P8699 [蓝桥杯 2019 国 B] 排列数|普及+
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!