P1521 求逆序对【洛谷算法习题】

📅 2026/7/24 18:51:51 👁️ 阅读次数 📝 编程学习
P1521 求逆序对【洛谷算法习题】

P1521 求逆序对

网页链接

P1521 求逆序对

题目描述

我们说( i , j ) (i,j)(i,j)a 1 , a 2 , ⋯ , a N a_1,a_2,\cdots,a_Na1,a2,,aN的一个逆序对,当且仅当i < j i<ji<ja i > a j a_i>a_jai>aj。例如[ 2 , 4 , 1 , 3 , 5 ] [2,4,1,3,5][2,4,1,3,5]的逆序对有3 33个,分别为( 1 , 3 ) , ( 2 , 3 ) , ( 2 , 4 ) (1,3),(2, 3), (2, 4)(1,3),(2,3),(2,4)。现在已知N NNK KK,求1 , 2 , 3 , ⋯ , N 1,2,3,\cdots,N1,2,3,,N的所有特定排列,使得这些排列的逆序对的数量恰好为K KK。输出这些特定排列的数量。

例如N = 5 N=5N=5K = 3 K=3K=3的时候,满足条件的排列有15 1515个,它们是:

  • [ 1 , 2 , 5 , 4 , 3 ] [1, 2, 5, 4, 3][1,2,5,4,3]
  • [ 1 , 3 , 4 , 5 , 2 ] [1, 3, 4, 5, 2][1,3,4,5,2]
  • [ 1 , 3 , 5 , 2 , 4 ] [1, 3, 5, 2, 4][1,3,5,2,4]
  • [ 1 , 4 , 2 , 5 , 3 ] [1, 4, 2, 5, 3][1,4,2,5,3]
  • [ 1 , 4 , 3 , 2 , 5 ] [1, 4, 3, 2, 5][1,4,3,2,5]
  • [ 1 , 5 , 2 , 3 , 4 ] [1, 5, 2, 3, 4][1,5,2,3,4]
  • [ 2 , 1 , 4 , 5 , 3 ] [2, 1, 4, 5, 3][2,1,4,5,3]
  • [ 2 , 1 , 5 , 3 , 4 ] [2, 1, 5, 3, 4][2,1,5,3,4]
  • [ 2 , 3 , 1 , 5 , 4 ] [2, 3, 1, 5, 4][2,3,1,5,4]
  • [ 2 , 3 , 4 , 1 , 5 ] [2, 3, 4, 1, 5][2,3,4,1,5]
  • [ 2 , 4 , 1 , 3 , 5 ] [2, 4, 1, 3, 5][2,4,1,3,5]
  • [ 3 , 1 , 2 , 5 , 4 ] [3, 1, 2, 5, 4][3,1,2,5,4]
  • [ 3 , 1 , 4 , 2 , 5 ] [3, 1, 4, 2, 5][3,1,4,2,5]
  • [ 3 , 2 , 1 , 4 , 5 ] [3, 2, 1, 4, 5][3,2,1,4,5]
  • [ 4 , 1 , 2 , 3 , 5 ] [4, 1, 2, 3, 5][4,1,2,3,5]

输入格式

输入共第一行,两个整数N NNK KK

输出格式

1 ⋯ N 1\cdots N1N的逆序对数量为K KK的特定排列的数量输出。为了避免高精度计算,请将结果对10000 1000010000取模后再输出。

输入输出样例 #1

输入 #1

5 3

输出 #1

15

说明/提示

数据范围及约定

对于全部数据,保证N ≤ 100 N \le 100N100K ≤ N × ( N − 1 ) / 2 K \le N\times (N-1)/2KN×(N1)/2

解题思路

本题是插入法动态规划 + 滑动窗口优化的经典题型,核心是将逆序对的生成过程转化为逐个插入最大元素的累加贡献,并用前缀和与对称性优化转移效率。

1. 问题等价转化
  • 逐步构造排列:考虑将数字1 ∼ N 1 \sim N1N按从小到大的顺序逐一插入到一个空序列中。由于第i ii个插入的数字i ii是当前最大的,无论它放在序列的哪个位置,都不会影响已存在数字之间的逆序关系。
  • 逆序对贡献:将i ii插入到长度为i − 1 i-1i1的序列中,有i ii个可能的插入位置。若插入在从右往左数第p pp个位置(p = 0 p=0p=0表示放在最右端,p = i − 1 p=i-1p=i1表示放在最左端),则会新产生p pp个逆序对(i ii大于前面p pp个数字)。
  • DP 定义:令g[i][j]表示1 ∼ i 1 \sim i1i的所有排列中,逆序对总数恰好为j jj的排列个数。则转移方程为:
    g [ i ] [ j ] = ∑ p = 0 min ⁡ ( j , i − 1 ) g [ i − 1 ] [ j − p ] g[i][j] = \sum_{p=0}^{\min(j,\,i-1)} g[i-1][j-p]g[i][j]=p=0min(j,i1)g[i1][jp]
    初值g[0][0] = g[1][0] = 1
2. 算法优化

直接按上述转移是O ( N 3 ) O(N^3)O(N3)的,不可接受。观察到转移是对前一行连续一段元素的求和,可以用滑动窗口优化到O ( N K ) O(NK)O(NK)

  • 递推式优化:对j ≥ 0 j \ge 0j0,有
    g [ i ] [ j ] = g [ i ] [ j − 1 ] + g [ i − 1 ] [ j ] − ( j ≥ i ? g [ i − 1 ] [ j − i ] : 0 ) g[i][j] = g[i][j-1] + g[i-1][j] - (j \ge i \;?\; g[i-1][j-i] \;:\; 0)g[i][j]=g[i][j1]+g[i1][j](ji?g[i1][ji]:0)
    这相当于用一个长度为i ii的窗口在g[i-1]上滑动求和。
  • 对称性加速:对于长度为i ii的排列,逆序对的最大值d [ i ] = i ( i − 1 ) 2 d[i] = \frac{i(i-1)}{2}d[i]=2i(i1),且分布完全对称,即g[i][j] = g[i][d[i]-j]。因此只需计算前一半(j ≤ d [ i ] / 2 j \le d[i]/2jd[i]/2)的值,后半部分直接复制,常数减半。
3. 算法步骤
  1. 初始化d[1]=0g[1][0]=1(代码里同时设了g[0][0]=1方便迭代)。
  2. 从小到大遍历i = 2 ∼ N i = 2 \sim Ni=2N
    • 计算最大逆序对数d[i] = d[i-1] + i - 1
    • j jj0 00d[i]/2,用滑动窗口公式计算g[i][j],同时注意每一步对g[i-1][j]取模(模数10000 1000010000)。
    • j jjd[i]/2 + 1d[i],通过对称性赋值g[i][j] = g[i][d[i]-j]
  3. 最后输出g[N][K] % 10000
4. 复杂度分析
  • 时间复杂度O ( N × K ) O(N \times K)O(N×K)N ≤ 100 N \le 100N100K KK最大约4950 49504950,计算量约5 × 10 5 5 \times 10^55×105,非常充裕。
  • 空间复杂度O ( N × K ) O(N \times K)O(N×K),存储 DP 表格。可以滚动数组优化至O ( K ) O(K)O(K),但本题空间限制宽裕,未做也无妨。

总结

将逆序对构造问题转化为逐个插入最大元素的贡献累加,利用 DP 进行计数。滑动窗口将转移优化成常数时间,对称性减少一半计算量。整体思路清晰,代码实现简洁。

代码简要说明

  1. 全局变量与数组

    • d[i]:长度为i ii的排列的最大逆序对数。
    • g[i][j]1 ∼ i 1 \sim i1i的排列中逆序对数为j jj的方案数(全程对10000 1000010000取模)。
  2. 核心循环

    • 外层i从 2 到N NN,计算d[i]
    • 内层j从 0 到d[i]/2
      • 先对g[i-1][j]取模。
      • 按滑动窗口公式计算g[i][j](注意g[i][j-1]已在前一步算好,需保证计算顺序)。
      • j >= i,减去窗口左侧溢出的项g[i-1][j-i]
    • 用对称性填充j > d[i]/2的部分。
  3. 输出cout << g[n][k] % 10000,确保取模。

代码内容

#include<bits/stdc++.h>usingnamespacestd;#defineendl'\n'typedeflonglongll;typedefunsignedlonglongull;typedefvector<vector<ll>>vvt;typedefpair<ll,ll>pll;constll N=1e3+10;constll INF=1e18;constll M=1e6+10;constll mod=1e9+7;ll n,k,d[105],g[105][5000];intmain(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n>>k;g[0][0]=g[1][0]=1;for(ll i=2;i<=n;i++){d[i]=d[i-1]+i-1;for(ll j=0;j<=d[i];j++){g[i-1][j]%=10000;if(j<=d[i]/2){g[i][j]=g[i-1][j]+g[i][j-1];if(j>=i)g[i][j]-=g[i-1][j-i];}elseg[i][j]=g[i][d[i]-j];}}cout<<g[n][k]%10000<<endl;return0;}