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

本文涉及知识点

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

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

题目描述

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

对于一个 1 ∼ n 1 ∼ n 1∼n 的排列,如果可以将这个排列中包含 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 1∼n 的所有排列中有多少个 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 1≤k≤n≤10;

对于 40 % 40 \% 40% 的评测用例, 1 ≤ k ≤ n ≤ 20 1 \leq k \leq n \leq 20 1≤k≤n≤20; 对于 60 % 60 \% 60% 的评测用例, 1 ≤ k ≤ n ≤ 100 1 \leq k \leq n \leq 100 1≤k≤n≤100;

对于所有评测用例, 1 ≤ k ≤ n ≤ 500 1 \leq k \leq n \leq 500 1≤k≤n≤500 。

蓝桥杯 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,i2⋯ik−1,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[i2...i3]a[i4...i5]⋯ 单调递增,其它单调递减。

情况二: 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[i2...i3]a[i4...i5]⋯ 单调递减,其它单调递增。
总结一 :共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=n−k−2

代码

核心代码

cpp 复制代码
#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;
};

单元测试

cpp 复制代码
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++**实现。

相关推荐
D_evil__7 小时前
【Effective Modern C++】第三章 转向现代C++:16. 让const成员函数线程安全
c++
Queenie_Charlie9 小时前
前缀和的前缀和
数据结构·c++·树状数组
kokunka10 小时前
【源码+注释】纯C++小游戏开发之射击小球游戏
开发语言·c++·游戏
John_ToDebug12 小时前
浏览器内核崩溃深度分析:从 MiniDump 堆栈到 BindOnce UAF 机制(未完待续...)
c++·chrome·windows
txinyu的博客13 小时前
解析muduo源码之 SocketsOps.h & SocketsOps.cc
c++
ctyshr13 小时前
C++编译期数学计算
开发语言·c++·算法
努力写代码的熊大13 小时前
c++异常和智能指针
java·开发语言·c++
John_ToDebug14 小时前
WebContent 与 WebView:深入解析浏览器渲染架构的双层设计
c++·chrome·ui
千秋乐。14 小时前
C++-string
开发语言·c++