[HAOI2009]逆序对数列

题目描述

对于一个数列 {a_{i}},如果有 i<j 且 a_{i}>a_{j},那么我们称与 a_{i},a_{j}为一对逆序对数。若对于任意一个由1∼n 自然数组成的数列,可以很容易求出有多少个逆序对数。那么逆序对数为 k 的这样自然数数列到底有多少个?

输入格式

第一行为两个整数n,k。

输出格式

写入一个整数,表示符合条件的数列个数,由于这个数可能很大,你只需输出该数对10000求余数后的结果。

样例

输入数据 1

4 1

输出数据 1

3

提示

------------------------------------------------------

分析:

        从题目大意来看,这是一个排列组合问题,我们用DP来解决。

        排列组合的话,当前状态与前面的状态都是有关联的,自然想到DP思想。

        我们用DP[i][j]来表示i个数列通过排序产生K个逆序对的排列总数。

        例如P[5][4] 表示有5个自然数,所有的排列中产生4个逆序对的排列的总和。

        假设A5是最大的数。

        那么在这5个数中,A5能放的位置有哪些呢?是不是只有下面几种情况。

        (#代表其他四个数)

        第一种:####A5   将A5放在最后

        第二种:###A5#  放在倒数第二

        第三种:##A5##    放在倒数第三

        第四种:#A5###     放在倒数第四

        第五种:A5####      放在第一个位置

        5个数的话,A5的位置是不是只有上面几种情况。

        接下来在每种情况下求出4个逆序对排列的数量,这把他们全部累积就是P[5][4]

        第一种情况:因为A5在最后位置,所以A5的位置不会影响前面四个数排列产生的逆序对数量。所以4个逆序对的排列数就是前面四个数排列来产生,即P[4][4]

        第二种情况:因为A5在倒数第二,所以A5#一定是一个逆序对,因为求的是4个逆序对,那么剩余的4个数必须产生3个逆序对,产生这3个逆序对的排列数是不是P[4][3]呢,P[4][3]就是第二种情况下排列数。

        第三种情况:因为A5在倒数第三,所以A5##一定可以产生2个逆序对,因为求的是4个逆序对,那么剩余的4个数必须产生2个逆序对,产生这2个逆序对的排列数是不是P[4][2]呢,P[4][2]就是第三种情况下排列数。

        第四种情况:因为A5在倒数第四,所以A5###一定可以产生3个逆序对,因为求的是4个逆序对,那么剩余的4个数必须产生1个逆序对,产生这1个逆序对的排列数是不是P[4][1]呢,P[4][1]就是第四种情况下排列数。

        第五种情况:因为A5在第一位置,所以A5####一定可以产生4个逆序对,因为求的是4个逆序对,那么剩余的4个数必须产生0个逆序对,产生这0个逆序对的排列数是不是P[4][0]呢,P[4][0]就是第五种情况下排列数。

        所以P[5][4]就等于上面四种情况下的排列和=P[4][4]+P[4][3]+P[4][2]+P[4][1]+P[4][0]

接下来,我们再扩展思考一下,如果是i个数,那么第i个数Ai,是不是可以分别放在下面这些位置:i,i-1,...,2,1。对于每个位置因为Ai产生的逆序对数量为:0,1,...,i-2,i-1。

如果Ai A1 A2 … Ai-2 Ai-1(Ai放在第一个位置),那么Ai与后面i-1个数都可以构成逆序对,也就总共有i-1个逆序对。如下

Ai与A1    
Ai与A2    
...    
Ai与Ai-2    
Ai与Ai-1

题目要求为i个数排列组合,产生k个逆序对的排列数。

那么Ai如果在最后,产生逆序对0个,那么余下的i-1个数就要产生K个逆序对才行 排列数:P[i-1][k]

那么Ai如果在倒数第二,产生逆序对1个,那么余下的i-1个数就要产生K-1个逆序对才行 排列数:P[i-1][k-1]

...

以此类推

那么Ai如果在第一个位置,产生逆序对i-1个,那么余下的i-1个数就要产生K-(i-1)个逆序对才行 排列数:P[i-1][k-(i-1)]

那么总的排列数P[i][k]=P[i-1][k]+P[i-1][k-1]+...+P[i-1][k-(i-2)]+P[i-1][k-(i-1)]   方程①

如果用k-1代替上面k的话,上面公式可以变为↓

P[i][k-1]=P[i-1][k-1]+P[i-1][k-1]+...+P[i-1][k-1-(i-2)]+P[i-1][k-1-(i-1)]  方程②

方程①-方程② 可以得到简化的通项公式

P[i][k]=P[i-1][k]+P[i][k-1]-P[i-1][k-i] 

有了这个通项公式,是不是就简单化了。

但是要注意,k<i的时候 P[i-1][k-i] 是无意义的。所以需要用if做个判断

因为这是反复迭代的问题,我们需要知道终止时的末项值才行,就是这个P[i][0],对于一个序列通过排序得到0个逆序对,一定只有一种排序方法,就是递增。所以P[i][0]=1。

Code

#include<bits/stdc++.h>
using namespace std;
int dp[1005][1005];
int main()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++) dp[i][0]=1;
	for (int i=2;i<=n;i++)
    {
        for(int j=1;j<=k;j++)
        {
            dp[i][j]=(dp[i][j-1]+dp[i-1][j])%10000;
            if(j>=i)
            dp[i][j]=(dp[i][j]-dp[i-1][j-i]+10000)%10000;
        }
    }
	cout<<dp[n][k];
}

补充说明:

        为什么(dp[i][j]-dp[i-1][j-i]+10000),需要加10000呢?思考过吗?

        因为(dp[i][j-1]+dp[i-1][j])%10000取模之后会将原本比较大的数变的比较小了,例如原本是100057的数变成了57,此时-dp[i-1][j-i]是57~9999的话,(dp[i][j-1]+dp[i-1][j])就变成了负数,为了防止出现负数,我们还是要用100057去减才能得到真正的数值。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这道题目还可以使用树状数组或线段树来实现,时间复杂度也为 $\mathcal{O}(n\log n)$。这里给出使用树状数组的实现代码。 解题思路: 1. 读入数据; 2. 将原数列离散化,得到一个新的数列 b; 3. 从右往左依次将 b 数列中的元素插入到树状数组中,并计算对数; 4. 输出对数。 代码实现: ```c++ #include <cstdio> #include <cstdlib> #include <algorithm> const int MAXN = 500005; struct Node { int val, id; bool operator<(const Node& other) const { return val < other.val; } } nodes[MAXN]; int n, a[MAXN], b[MAXN], c[MAXN]; long long ans; inline int lowbit(int x) { return x & (-x); } void update(int x, int val) { for (int i = x; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) { res += c[i]; } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); nodes[i] = {a[i], i}; } std::sort(nodes + 1, nodes + n + 1); int cnt = 0; for (int i = 1; i <= n; ++i) { if (i == 1 || nodes[i].val != nodes[i - 1].val) { ++cnt; } b[nodes[i].id] = cnt; } for (int i = n; i >= 1; --i) { ans += query(b[i] - 1); update(b[i], 1); } printf("%lld\n", ans); return 0; } ``` 注意事项: - 在对原数列进行离散化时,需要记录每个元素在原数列中的位置,便于后面计算对数; - 设树状数组的大小为 $n$,则树状数组中的下标从 $1$ 到 $n$,而不是从 $0$ 到 $n-1$; - 在计算对数时,需要查询离散化后的数列中比当前元素小的元素个数,即查询 $b_i-1$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值