题目描述
n 个数有 n! 种全排列情况,对所有排列排序后求第 L 个到第 R 个排列中逆序对数量之和。逆序对定义(摘自 wiki):
设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。
如果存在正整数 i,j 使得 1≤i<j≤n 而且 Ai>Aj,则 (Ai,Aj) 这一个有序对称为 A 的一个逆序对,也称作逆序。逆序对的数量称作逆序数。
输入
第一行 case 数量 T。接下来每一行有 3 个数,n,L,R (3≤n≤12,1≤L≤R≤109)。
输出
输出逆序对总数。
样例输入
3 3 3 5 6 720 720 8 14625 17743
样例输出
5 15 38745
提示
样例 1 说明:
3 个数所有排列排序后及其逆序对个数:
(1,2,3): 0;
(1,3,2): 1;
(2,1,3): 1;
(2,3,1): 2;
(3,1,2): 2;
(3,2,1): 3.
第 3 个到第 5 个排列逆序对数量之和为 1+2+2=5。
来源/分类
参考博客:https://www.cnblogs.com/lglh/p/10279781.html
#include "bits/stdc++.h" using namespace std; typedef long long ll; ll fac[20], invnum[20];//fac为阶乘,invnum[i]为长度为i的数组的所有排列情况的逆序对之和, int n; int vis[20]; void init() { fac[0] = 1; fac[1] = 1; invnum[1] = 0; invnum[0] = 0; for (int i = 2; i <= 12; i++) { fac[i] = i * fac[i - 1]; invnum[i] = fac[i - 1] * (i * (i - 1)) / 2 + i * invnum[i - 1];//将数组分为两段,前面一段长度为1,后面一段长度为i-1 //fac[i-1] * (i * (i - 1)) / 2 任取两数,将大的放在第一个,乘以后面i-1个数的全排列,得其对总逆序数的贡献 //i * invnum[i - 1] 任取一数放在第一个,其余i-1个数对总逆序数的贡献 } } ll slove(ll t) { ll res = 0; int pre[20]; for (int i = 1; i <= n; i++) {//枚举前缀长度 for (int j = 1; j <= n; j++) {//枚举前缀元素 bool flag = false; for (int l = 1; l < i; l++) { if (pre[l] == j) { flag = true;//元素只能出现一次 break; } } if (flag) continue; pre[i] = j; if (t < fac[n - i]) break; else {//将数列分为前i个与后n-i个两部分计算总逆序数 t -= fac[n - i]; res += invnum[n - i]; ll cnt = 0; for (int k = 1; k <= i; k++) { for (int l = k; l <= i; l++) { if (pre[l] < pre[k]) cnt++; } } memset(vis, 0, sizeof(vis)); for (int k = 1; k <= i; k++) { vis[pre[k]] = 1; } for (int k = 1; k <= n; k++) { for (int l = 1; l < k; l++) { if (vis[k] && !vis[l]) cnt++; } } res += cnt * fac[n - i]; } } } return res; } int main() { freopen("input.txt", "r", stdin); int _; scanf("%d", &_); ll l, r; init(); while (_--) { scanf("%d %lld %lld", &n, &l, &r); printf("%lld\n", slove(r) - slove(l - 1)); } return 0; }