通过一个case解释算法过程.
比如 case :
5 9
第一步 ,序列为 1 2 3 4 5, 逆序数为 9
输出序列中第4个数4,因为 4后面有3个比4小的,而且删掉4后最大还可以得到6个逆序(3+6>=9),所以4符合条件,5也符合,但是5比4大,
3或更小不符合.
第二步,序列为1 2 3 5, 逆序数为9-3=6
输出序列中第4个数5,计算同上.
...
以上每一步计算出应该输出序列中第几个数可以O(1)时间实现,但是对于计算序列中第n个数是几,实现为O(N),N=50000超时,使用线段树
可以在O(logN)时间内计算出来,程序的时间复杂度为O(N*logN),得解.
源代码:
View Code
//使用线段树解逆序数问题
//线段树功能是,在O(logN)的时间内在一个链表中
//找出第n个元素,
//同时可解约瑟夫环问题,如 POJ_3750
#include <iostream>
#include <fstream>
#include <algorithm>
#include <cstring>
#include <climits>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long LL;
typedef struct node
{
LL left, right, mid;
LL cnt;//记录本区间内还有几个剩余
inline LL len()//区间没有删掉元素时的长度
{
return right - left + 1;
}
}node;
node tree[1000000];
LL N,M;
void create(LL left, LL right, LL x)
{
tree[x].left = left, tree[x].right = right;
tree[x].cnt = right - left + 1;
tree[x].mid = (left + right) / 2;
if (left == right)
return ;
create(left, tree[x].mid, x * 2);
create(tree[x].mid + 1, right, x * 2 + 1);
}
LL get(LL s, LL x)//计算对应相应的s,在区间内第s个数是几
{
if (tree[x].cnt == tree[x].len())
{
return tree[x].left + s - 1;
}
if (tree[x * 2].cnt >= s)
return get(s, x * 2);
return get(s - tree[x * 2].cnt, 2 * x + 1);
}
void del(LL s,LL x)//在区间内删掉s
{
tree[x].cnt--;
if (tree[x].len() == 1)
return ;
if (s <= tree[x].mid)
del(s, x * 2);
else
del(s,x * 2 + 1);
}
LL calc(LL x, LL y)//注意使用long long
{
LL res = y - (x - 1) * (x - 2) / 2;
if(res < 0)
return 1;
return res + 1;
}
int main()
{
// freopen("input.txt", "r", stdin);
while(scanf("%I64d %I64d", &N, &M) != EOF)
{
if(-1 == N && -1 == M)
break;
LL x, y;
create(1, N, 1);
for(LL i = 0; i < N; i++)
{
x=calc(N - i,M);
y=get(x, 1);
if(i)
putchar(' ');
printf("%I64d",y);
del(y, 1);
M -= x - 1;
}
putchar('\n');
}
return 0;
}