宝塔服务器面板,一键全能部署及管理,送你10850元礼包,点我领取
一、错排是什么
错排是组合数学中的一种常用问题,它指将n个元素排列在n个位置上,其中每个元素最多出现一次,有多少种不同排列情况。通俗的说,就是在所有的排列中,有多少种情况下,没有元素排在它原本的位置上。
/** * 阶乘 */ int factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } return result; } /** * 错排公式 */ int deRange(int n) { if (n == 1) { return 0; } else if (n == 2) { return 1; } else { return (n - 1) * (deRange(n - 1) + deRange(n - 2)); } }
二、错排公式的推导
下面我们以n=4为例,来推导一下错排公式。
我们要求的是4个元素排列的错排数。
首先,第一个元素有3种可能,它可以排在除了第一个位置之外的任意一个位置上,那么第一个元素排完之后,问题就转化为了剩下的3个元素的错排,总数就是3*(3-1) = 6。
如果第一个元素排在第二个位置上,那么它就不能排在第一个位置和第二个位置,那么它就只能排在第三个位置或者第四个位置上。如果排在第三个位置上,剩下的元素需要错排,错排的总数就是2*(2-1) = 2;如果排在第四个位置上,剩下的元素也需要错排,错排的总数也是2*1 = 2。
所以,第一个元素排在第二个位置的总数就是4。
同理,如果第一个元素排在第三个位置和第四个位置,那么错排的总数也是4。
最终,我们得到的结果就是 3*(3-1)*2 + 4*(2-1)*2 + 4*(2-1)*2 = 18+8+8 = 34
三、程序实现
这里我们使用递归的方式来实现错排公式。注意,当n=1时,返回值为0;n=2时,返回值为1。
int deRange(int n) { if (n == 1) { return 0; } else if (n == 2) { return 1; } else { return (n - 1) * (deRange(n - 1) + deRange(n - 2)); } } // 输出n个元素的错排数 int main() { int n; scanf("%d", &n); printf("%d\n", deRange(n)); return 0; }
四、使用错排公式的一些场景
错排公式可以用于多种场合,下面我们介绍几个常见的使用情况。
1、密码学
错排公式可以用于密码学,当我们需要把一个明文字符串加密成一个密文字符串时,可以使用错排公式来产生一个随机的排列方式,然后将所有的字符按照这种排列方式进行替换,从一个字符串变成另一个字符串。
// 将字符串s按照错排公式进行加密 string encode(string s) { int n = s.size(); // 产生一个随机的排列方式 vector p(n); for (int i = 0; i < n; i++) { p[i] = i; } random_shuffle(p.begin(), p.end()); // 将所有字符按照排列方式进行替换 string t(n, ' '); for (int i = 0; i < n; i++) { t[p[i]] = s[i]; } return t; } // 将字符串s按照给定的排列方式进行解密 string decode(string s, vector p) { int n = s.size(); string t(n, ' '); for (int i = 0; i < n; i++) { t[i] = s[p[i]]; } return t; }
2、贡献值排序
在有些场合下,我们需要对一组数据进行排序,但是需要考虑到每个元素的贡献值。例如,在一堆学生中,有些学生的成绩比较高,但是参与过的活动比较少,有些学生的成绩比较低,但是参与过的活动比较多,那么我们要按照这两项指标分别对学生进行评价,如何给出一个合理的排序呢?错排公式可以提供一种思路,即将高成绩的学生看作是排列在前面的元素,将参与活动多的学生看作排列在后面的元素,然后使用错排公式计算出所有满足要求的排列数量,根据排列数量的大小来进行排序。
// 对一组数据按照权值进行排序 template vector<pair> sortByWeight(const vector &a, function getWeight) { int n = a.size(); vector<pair> w(n); for (int i = 0; i < n; i++) { w[i] = make_pair(getWeight(a[i]), a[i]); } sort(w.rbegin(), w.rend()); // 按照权值从大到小排序 double totalWeight = accumulate(w.begin(), w.end(), 0.0, [](double x, pair y) { return x + y.first; }); vector<pair> result(n); for (int i = 0; i < n; i++) { double leftWeight = totalWeight - w[i].first; // 排列在后面的元素的贡献值之和 double rightWeight = w[i].first; // 排列在前面的元素的贡献值 int leftCount = n - 1 - i; // 排列在后面的元素的个数 int rightCount = i; // 排列在前面的元素的个数 int totalCount = leftCount + rightCount; result[i].first = (double) deRange(totalCount) / factorial(totalCount) * leftWeight + (double) deRange(leftCount) / factorial(leftCount) * rightWeight; result[i].second = w[i].second; } sort(result.rbegin(), result.rend()); // 按照错排数量从大到小排序 return result; }
3、图像隐写
图像隐写是将一张图片嵌入到另一张图片中,使得外观上看起来像一张图片。错排公式可以提供一种方式,根据错排公式的结果,将原始图片的像素点随机地替换成隐写图片的像素点。
// 将图片t嵌入到图片s中 Mat embed(Mat s, Mat t) { srand(time(NULL)); int n = s.rows * s.cols; vector p(n); for (int i = 0; i < n; i++) { p[i] = i; } random_shuffle(p.begin(), p.end()); Mat result = s.clone(); for (int i = 0; i < n / 2; i++) { int x1 = p[2 * i] / s.cols; int y1 = p[2 * i] % s.cols; int x2 = p[2 * i + 1] / s.cols; int y2 = p[2 * i + 1] % s.cols; result.at(x1, y1) = t.at(x2 % t.rows, y2 % t.cols); result.at(x2, y2) = t.at(x1 % t.rows, y1 % t.cols); } return result; }
最新评论