zoukankan      html  css  js  c++  java
  • UVA11525 Permutation[康托展开 树状数组求第k小值]

    UVA - 11525

     题意:输出1~n的所有排列,字典序大小第∑k1Si∗(Ki)!个


    学了好多知识

    1.康托展开

    X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!

    其中a[i]为第i位是i往右中的数里 第几大的-1(比他小的有几个)。

    其实直接想也可以,有点类似数位DP的思想,a[n]*(n-1)!也就是a[n]个n-1的全排列,都比他小

    一些例子 http://www.cnblogs.com/hxsyl/archive/2012/04/11/2443009.html

    如我想知道321是{1,2,3}中第几个大的数可以这样考虑 :

      第一位是3,当第一位的数小于3时,那排列数小于321 如 123、 213 ,小于3的数有1、2 。所以有2*2!个。再看小于第二位2的:小于2的数只有一个就是1 ,所以有1*1!=1 所以小于321的{1,2,3}排列数有2*2!+1*1!=5个 
    。所以321是第6个大的数。 2*2!+1*1!是康托展开。

      再举个例子:1324是{1,2,3,4}排列数中第几个大的数:第一位是1小于1的数没有,是0个 0*3! 第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2 1*2! 。第三位是2小于2的数是1,但1在第一位,所以 
    有0个数 0*1! ,所以比1324小的排列有0*3!+1*2!+0*1!=2个,1324是第三个大数。


    2.树状数组求区间第k小

    查了一堆资料,稍微有点明白了

    用a[i]表示i出现次数,用一个树状数组c[]维护a[]

    设第k小为x,那么sum(x)=k

    我们的做法是用二进制反向构造x

    cnt是当前x的排名(<=x的个数)

    把x打成二进制,从高到低枚举(1<<i),可以发现x加上(1<<i)后lowbit值是不断减小的(lowbit的定义是最右面1对应的值)

    cnt加上新x的c值(这一块二进制加上的效果相当于让x在树状数组上跳了一下,结果也就是当前sum(x)的结果)

    if(x>=n||cnt+c[x]>=k) 就不加了

    这样就找到了最大的比x小的元素,++就行了

    其实就记住,用二进制构造x-1,c数组类比着加就行了

    二进制逼近,反向求和的过程

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    const int N=5e5+5,INF=1e6+5;
    inline int read(){
        char c=getchar();int x=0,f=1;
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
        return x*f;
    }
    int n,x,k;
    int c[N];
    inline int lowbit(int x){return x&-x;}
    inline void add(int p,int v){
        for(;p<=n;p+=lowbit(p)) c[p]+=v;
    }
    inline int sum(int p){
        int res=0;
        for(;p>0;p-=lowbit(p)) res+=c[p];
        return res;
    }
    inline int kth(int k){
        int x=0,cnt=0;
        for(int i=16;i>=0;i--){
            x+=(1<<i);
            if(x>=n||cnt+c[x]>=k) x-=(1<<i);
            else cnt+=c[x];
        }
        return x+1;
    }
    int main(){
        int T=read();
        while(T--){
            n=read();
            memset(c,0,sizeof(c));
            for(int i=1;i<=n;i++) add(i,1);
            for(int i=1;i<=n;i++){
                k=read()+1;
                x=kth(k);
                printf("%d%c",x,i==n?'
    ':' ');
                add(x,-1);
            }
        }
    }
  • 相关阅读:
    Sql Server 2008卸载后再次安装一直报错
    listbox 报错 Cannot have multiple items selected when the SelectionMode is Single.
    Sql Server 2008修改Sa密码
    学习正则表达式
    Sql Server 查询第30条数据到第40条记录数
    Sql Server 复制表
    Sql 常见面试题
    Sql Server 简单查询 异步服务器更新语句
    jQuery stop()用法以及案例展示
    CSS3打造不断旋转的CD封面
  • 原文地址:https://www.cnblogs.com/candy99/p/6067214.html
Copyright © 2011-2022 走看看