洛谷 P2513 ——逆序对数列

题目描述
对于一个数列 { a i } \{a_i\} {ai},如果有 i < j i<j i<j a i > a j a_i>a_j ai>aj,那么我们称 a i a_i ai a j a_j aj ​ 为一对逆序对数。

若对于任意一个由 1 ∼ n 1∼n 1n 自然数组成的数列,可以很容易求出有多少个逆序对数。

那么逆序对数为 k k k 的这样自然数数列到底有多少个?

输入格式
第一行为两个整数 n , k n,k nk

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

输入样例
4 1

输出样例
3

样例说明
下列 3 3 3 个数列逆序对数都为 1 1 1,分别是:

  • 1 2 4 3 ;
  • 1 3 2 4 ;
  • 2 1 3 4;

数据范围
n , k ≤ 1000 n,k≤1000 nk1000


题解一(超时):线性DP

f [ i ] [ j ] : 由 1 ∼ i 组 成 的 逆 序 对 数 量 为 j 的 数 列 个 数 f[i][j]:由1∼i组成的逆序对数量为j的数列个数 f[i][j]1ij

状 态 转 移 : 状态转移:

  • 考 虑 第 i 个 数 的 放 置 情 况 , 由 于 i 的 数 值 最 大 , 因 此 根 据 不 同 的 放 置 位 置 , 可 以 产 生 的 逆 序 对 数 量 为 0 ∼ i − 1 考虑第i个数的放置情况,由于i的数值最大,因此根据不同的放置位置,可以产生的逆序对数量为0∼i-1 ii0i1
  • 因 此 f [ i ] [ j ] + =   ∑ r = j − ( i − 1 ) j ( f [ i − 1 ] [ r ] ) 因此f[i][j]+=\ \sum_{r=j-(i-1)}^{j}{(f[i-1][r])} f[i][j]+= r=j(i1)j(f[i1][r])
#include <bits/stdc++.h>
using namespace std;

const int N = 1010, MOD = 1e4;

int n, k;
int dp[N][N];

int main()
{
	cin >> n >> k;
	
	dp[1][0] = 1;
	for (int i = 2; i <= n; i ++)
		for (int j = 0; j <= k; j ++)
			for (int r = j - (i - 1); r <= j; r ++)
				if(r >= 0) 
					dp[i][j] = (dp[i][j] + dp[i - 1][r]) % MOD;
				
	cout << dp[n][k] << endl;
	return 0;			
}

题解二:线性DP(优化)

  • 由 于 是 从 j 开 始 , 往 前 数 连 续 i 个 数 由于是从j开始,往前数连续i个数 ji
  • 因 此 , 当 计 算 f [ i ] [ j + 1 ] 的 时 候 , 是 在 f [ i ] [ j ] 的 基 础 上 , 要 先 加 上 f [ i − 1 ] [ j + 1 ] , 再 减 去 f [ i − 1 ] [ j − ( i − 1 ) ] 因此,当计算f[i][j+ 1]的时候,是在f[i][j]的基础上,要先加上f[i-1][j+1],再减去f[i-1][j-(i-1)] f[i][j+1]f[i][j]f[i1][j+1]f[i1][j(i1)]
  • 所 以 在 计 算 f [ i ] [ j ] 时 , 要 先 加 上 f [ i − 1 ] [ j ] , 再 减 去 f [ i − 1 ] [ j − i ] 所以在计算f[i][j]时,要先加上f[i-1][j],再减去f[i-1][j-i] f[i][j]f[i1][j]f[i1][ji]
f[i][j]     = f[i - 1][j - (i - 1)] + f[i - 1][j - (i - 2)] + ... + f[i - 1][j - 1] + f[i - 1][j]
f[i][j + 1] =                         f[i - 1][j - (i - 2)] + ... + f[i - 1][j - 1] + f[i - 1][j] + f[i - 1][j + 1]
#include <bits/stdc++.h>
using namespace std;

const int N = 1010, MOD = 1e4;

typedef long long LL;

int n, k;
int dp[N][N];

int main()
{
	cin >> n >> k;
	
	dp[1][0] = 1;
	for (int i = 2; i <= n; i ++)
	{
		LL sum = 0;
		for (int j = 0; j <= k; j ++)
		{
			sum += dp[i - 1][j];
			if(j - i >= 0) sum -= dp[i - 1][j - i];
			dp[i][j] = sum % MOD;
		}
	}
				
	cout << dp[n][k] << endl;
	return 0;			
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这道题目还可以使用树状数组或线段树来实现,时间复杂度也为 $\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$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值