UVA 11525 好大好大的排列(线段树)

题目在这

题意:求1-k的排列中第n大的序列,题目给出n的计算方法:
n = si*(k-1)+s2*(k-2)…+sk*0!; 并给你s1~sk

**分析:**n好大好大,,,,,
n是给的一个一个的阶乘和,想想:k位的排列数是k!
从0!到(k-1)!,,,似乎,对应着什么
每一个s[i]对应全排列的一位数字
(蚊子数学太弱,,,证明什么的,,,额)
不过看到一个一个阶乘还是可以yy出这么一个写法的:
每读入一个s[i],选取剩下的数中第s[i]+1小的,
有点拗口是吧,,,拿样例模拟模拟,就搞出来了

k很大,有5w,如果每次都赤裸裸的找的话,
平方复杂度必然TLE,开个线段树吧,
权值是0或1,1表示没被用过,0表示被用过了
维护区间和。。。。每次取剩下的第s[i]+1个数

#include<cstdio>
#include<cstring>
#include<iostream> 
#include<algorithm>
using namespace std;
const int N=140000;
int ans;

struct tree{
    int su[N],l[N],r[N],M;

    void build(int n){
        M=1; while(M<n)M<<=1; M--;
        for (int i=M+1;i<=M+n;i++)l[i]=r[i]=i-M,su[i]=1;
        for (int i=M+n+1;i<=M*2+1;i++)l[i]=r[i]=i-M,su[i]=0;
        for (int i=M;i>=1;i--){
            l[i]=l[i<<1],r[i]=r[i<<1^1];
            su[i]=su[i<<1]+su[i<<1^1];
        }
    }

    void get(int t,int k,int sum){
        if (l[t]==r[t]){
            ans=l[t];su[t]=0;return;
        }
        int mid=(l[t]+r[t])>>1;
        if (sum+su[t<<1]>=k)get(t<<1,k,sum);
        else get(t<<1^1,k,sum+su[t<<1]);
        su[t]=su[t<<1]+su[t<<1^1];
    }
}T;

int main(){
    int cas;scanf("%d",&cas);
    while (cas--){
        int n;scanf("%d",&n);
        T.build(n);
        for (int i=1;i<=n;i++){
            int x;scanf("%d",&x);
            T.get(1,x+1,0);
            printf("%d",ans);
            if (i<n)printf(" "); 
        }
        puts("");
    }
    return 0;
}
原文地址:https://www.cnblogs.com/cww97/p/12349423.html