poj2085

题意:给定n、m,求1~n的一个排列,逆序数对数为m,且该排列的字典序最小。

分析:若逆序数对数为m,则每个数右侧比其大的数的个数之和应为k=n*(n-1) - m(正序数对数)。我们可以想象,如果把k拆分成若干个数字的和,并把这些数字随意分配给n个位置,来代表该位置右侧比该位置上的数字大的数的个数,那么这定能构成一个逆序数对数为m的排列。若想要字典序最小那么就要尽量把小的数往前放。小的数也就是其右侧比其大的数较多的数。如果用拆分k的方法,则应该给第一个位置分配最大的数,然后给第二个位置分配最大的数……然而每个位置分配的数是有限制的,因为对于位置i其右侧有n-i个数,也就是最多只能有n-i个数比它大,所以分配给该位置的数不能超过n-i。为了字典序最小我们就从左到右给每个位置分配其最大值,直至k被分配完了为止。这样就形成了一个字典序最小的逆序数对数为k的排列,但是我们并没有确切地知道这个排列是什么,只是知道了每个位置右侧比该位置上的数字大的数的个数。下面我们就来还原这个序列。这个分配k的方式有个特点,就是左面都是最大值,右面都是0。所以对于左面连续的最大值部分,我们只需要按照顺序将1~x填入其中即可(对于i位置,右侧有n-i个数字比它大,左侧所有的数字都比它小,那么它一定是i)。对于这个数组0与最大值的交界处会有一个不是最大值也不是0的数字y。我们把n-y填入答案的这个格,由于左侧数字都比它小,右侧有y个比它大的。然后对于所有的零只需将剩余的没有使用过的数字,按从大到小的顺序依次填入即可。

View Code
#include <iostream>
#include
<cstdio>
#include
<cstdlib>
#include
<cstring>
usingnamespace std;

#define maxn 50005

longlong n, m;
longlong f[maxn];
bool vis[maxn];
int ar[maxn];

void work()
{
memset(vis,
0, sizeof(vis));
int x = n *(n -1) /2- m;
int p =-1;
for (int i =0; i < n; i++)
{
if (x >= n - i -1)
{
f[i]
= i +1;
x
-= n - i -1;
vis[f[i]]
=true;
}
else
{
p
= i;
break;
}
}
if (p ==-1)
return;
f[p]
= n - x;
vis[f[p]]
=true;
int s = n;
for (int i = p +1; i < n; i++)
{
while (vis[s])
s
--;
vis[s]
=true;
f[i]
= s;
}
}

void print()
{
printf(
"%lld", f[0]);
for (int i =1; i < n; i++)
printf(
" %lld", f[i]);
putchar(
'\n');
}

int main()
{
//freopen("t.txt", "r", stdin);
while (scanf("%lld%lld", &n, &m), !(n ==-1&& m ==-1))
{
work();
print();
}
return0;
}
原文地址:https://www.cnblogs.com/rainydays/p/2155440.html