POJ 2104&HDU 2665 Kth number(主席树入门+离散化)

K-th Number
Time Limit: 20000MS Memory Limit: 65536K
Total Submissions: 50247 Accepted: 17101
Case Time Limit: 2000MS

Description

You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment. 
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?" 
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000). 
The second line contains n different integer numbers not exceeding 10 9 by their absolute values --- the array for which the answers should be given. 
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

Sample Input

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output

5
6
3

Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

 

 

题目链接:POJ 2104    HDU 2665(HDU题面描述有误,虽说是求kth big其实还是跟POJ一样求kth small)

看主席树看了N久,终于把更新操作和查询操作看懂了:),其间找了无数篇博客但是其中绝大部分都是泛泛而谈有一些甚至只有代码连注释都没有,

很多次想弃疗(感觉这高大上的数据结构弱比我估计以后很难用到),然而还是没有放弃终于看懂一点点……故想通过这两道模版题来说一下弱比我的理解……

首先要知道普通线段树如何求整个范围内第k大,单点更新+单点查询(至叶子节点),然后就是这个区间第K大(小)怎么弄呢?由于单点更新的时候只会更新一条路上的东西即树上一层只会更新一个节点,比如某一层有两个节点来管理【1,2】与【 3,4】,假设更新1这个值,显然【3,4】是不会变的,只是管理【1,2】这个节点的cnt值加1,当然,这个节点下面还有【1,1】与【2,2】这俩叶子,同样道理也只会更新管理【1,1】的节点,使它的cnt值加1,管理【2,2】的节点不动……

根据二叉树的性质可以知道,每一次修改就只要新建出Log(N)左右个节点就可以与之前的树形成一个新的树,树上的老节点就是不用更新的,新节点就是更新的再指向老的节点(即很可能会与老节点共用子节点)。然后就用到了前缀和的思想,一个元素均不相同的序列可以发现这样一个规律,假设10个数为1 3 5 7 9 2 4 6 8 10,先简化一下问题,求【3,7】的节点cnt为多少,那显然是【1,7】.cnt-【1,(3-1)】.cnt=5,继而就可以求【3,4】、【5,7】甚至更后面的小区间内cnt值,那问题就成了一个前面讲的线段树求整个范围内第k大,只是整个范围一直被维护为L,R之间而已。

主席树这种写法需要root数组、和lson与rson的数组,还有记录区间内数字出现个数的cnt数组

更新操作:首先复制当前所在原节点的所有信息,再将cnt+1(因为你在原来的基础上插入了一个数),然后用单点更新的思路进行复制并更新(讲复制感觉更贴切实际,因为更新是在副本上更新的而不是原来节点上),走到哪就把哪个节点复制下来再在这个副本上进行更新

查询操作:用两颗不同的树的cnt作差再判断查询方向,写法上跟普通的单点查询非常相似

最后题目数据范围比较大需要离散化一下另外我加了个没什么卵用的读入外挂……

POJ 2104代码:

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <vector>
using namespace std;
#define INF 0x3f3f3f3f
#define CLR(x,y) memset(x,y,sizeof(x))
#define LC(x) (x<<1)
#define RC(x) ((x<<1)+1)
#define MID(x,y) ((x+y)>>1)
typedef pair<int,int> pii;
typedef long long LL;
const int N=100010;
struct seg
{
    int lson,rson;
    int cnt;
};
seg T[N*20];
int tot,rt[N];
int arr[N];
vector<int>pos;
void init()
{
    tot=0;
    CLR(rt,0);
    pos.clear();
}
int Scan()
{
    int res=0,ch,flag=0;
    if((ch=getchar())=='-')
        flag=1;
    else if(ch>='0'&&ch<='9')
        res=ch-'0';
    while((ch=getchar())>='0'&&ch<='9')
        res=res*10+ch-'0';
    return flag?-res:res;
}
void build(int &cur,int l,int r)//注意第一个参数是引用为了修改原值
{
    cur=++tot;
    T[cur].cnt=0;
    if(l==r)
        return ;
    else
    {
        int mid=MID(l,r);
        build(T[cur].lson,l,mid);
        build(T[cur].rson,mid+1,r);
    }
}
void update(int &cur,int ori,int l,int r,int val)//cur为准备新开的节点,ori为原来对应位置的节点
{
    cur=++tot;//新建一颗树
    T[cur].lson=T[ori].lson;//复制信息
    T[cur].rson=T[ori].rson;//复制信息
    T[cur].cnt=T[ori].cnt+1;//更新新增值
    if(l==r)
        return ;
    else
    {
        int mid=MID(l,r);
        if(val<=mid)
            update(T[cur].lson,T[ori].lson,l,mid,val);
        else
            update(T[cur].rson,T[ori].rson,mid+1,r,val);
    }
}
int query(int st,int ed,int l,int r,int k)
{
    if(l==r)
        return l;
    else
    {
        int mid=MID(l,r);
        int cnt=T[T[ed].lson ].cnt-T[T[st].lson ].cnt;//与普通求Kth一样先看lson(看rson也行下面顺序变一下而已)
        if(k<=cnt)
            return query(T[st].lson,T[ed].lson,l,mid,k);
        else
            return query(T[st].rson,T[ed].rson,mid+1,r,k-cnt);
    }
}
int main(void)
{
    int n,m,i,l,r,k;
    while (~scanf("%d%d",&n,&m))
    {
        for (i=1; i<=n; ++i)
        {
            scanf("%d",&arr[i]);
            pos.push_back(arr[i]);
        }

        sort(pos.begin(),pos.end());//pos储存着原值
        pos.erase(unique(pos.begin(),pos.end()),pos.end());
        for (i=1; i<=n; ++i)
            arr[i]=lower_bound(pos.begin(),pos.end(),arr[i])-pos.begin()+1;//arr变为离散化之后的数组仅用来更新

        int SZ=pos.size();
        build(rt[0],1,SZ);

        for (i=1; i<=n; ++i)
            update(rt[i],rt[i-1],1,SZ,arr[i]);
        for (i=0; i<m; ++i)
        {
            l=Scan();
            r=Scan();
            k=Scan();
            int indx=query(rt[l-1],rt[r],1,SZ,k)-1;//由于pos下标从0开始
            printf("%d\n",pos[indx]);//再映射回去
        }
        init();
    }
    return 0;
}

 

搞半天发现build没什么卵用……直接update也行,离散化也优化了一下,简化了HDU的代码,速度快了不少

HDU代码:

#include <stdio.h>
#include <algorithm>
#include <vector>
#include <string.h>
using namespace std;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
#define MID(x,y) ((x+y)>>1)

const int N=1e5+7;
struct seg
{
    int lson,rson;
    int cnt;
};
seg T[N*20];
int tot,root[N];
int arr[N];
vector<int>vec;

void init()
{
    tot=0;
    vec.clear();
    CLR(root,0);
}
void update(int &cur,int ori,int l,int r,int val)
{
    cur=++tot;
    T[cur]=T[ori];
    ++T[cur].cnt;
    if(l==r)
        return ;
    int mid=MID(l,r);
    if(val<=mid)
        update(T[cur].lson,T[ori].lson,l,mid,val);
    else
        update(T[cur].rson,T[ori].rson,mid+1,r,val);
}
int query(int L,int R,int l,int r,int k)
{
    if(l==r)
        return l;
    int mid=MID(l,r);
    int cnt=T[T[R].lson].cnt-T[T[L].lson].cnt;
    if(k<=cnt)
        return query(T[L].lson,T[R].lson,l,mid,k);
    else
        return query(T[L].rson,T[R].rson,mid+1,r,k-cnt);
}
int main(void)
{
    int T,n,m,l,r,k,i;
    scanf("%d",&T);
    while (T--)
    {
        init();
        scanf("%d%d",&n,&m);
        for (i=1; i<=n; ++i)
        {
            scanf("%d",&arr[i]);
            vec.push_back(arr[i]);
        }

        sort(vec.begin(),vec.end());
        vec.erase(unique(vec.begin(),vec.end()),vec.end());

        int R=vec.size()-1;//直接离散为0~(size-1)方便后面就不用搞什么+1-1了
        //build(root[0],0,R);
        for (i=1; i<=n; ++i)
        {
            arr[i]=lower_bound(vec.begin(),vec.end(),arr[i])-vec.begin();
            update(root[i],root[i-1],0,R,arr[i]);
        }
        for (i=0; i<m; ++i)
        {
            scanf("%d%d%d",&l,&r,&k);
            int indx=query(root[l-1],root[r],0,R,k);
            printf("%d\n",vec[indx]);
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/Blackops/p/5929126.html

多角色体系 支持管理员、商家、消费者三种角色,权限分级管控: 管理员:负责平台整体配置、用户审核、数据监控等全局操作。 商家:管理店铺信息、发布商品、处理订单、回复评价等。 消费者:浏览商品、加入购物车、下单支付、评价商品等。 实现用户注册(手机号 / 邮箱验证)、登录(支持密码 / 验证码 / 第三方登录)、个人信息管理(头像、收货地址、密码修改)。 权限精细化控制 商家仅能管理自家店铺及商品,消费者仅能查看和购买商品,管理员拥有全平台数据访问权限。 二、商品管理功能 商品信息维护 商家可发布商品:填写名称、分类(如服饰、电子产品)、子类别(如手机、笔记本)、规格(尺寸、颜色、型号)、价格、库存、详情描述(图文)、物流信息(运费、发货地)等。 支持商品上下架、库存调整、信息编辑,系统自动记录商品状态变更日志。 商品分类与搜索 按多级分类展示商品(如 “数码产品→手机→智能手机”),支持自定义分类体系。 提供智能搜索功能:按关键词(名称、品牌)搜索,支持模糊匹配和搜索联想;结合用户浏览历史对搜索结果排序(优先展示高相关度商品)。 商品推荐 基于用户浏览、收藏、购买记录,推荐相似商品(如 “浏览过该商品的用户还买了…”)。 首页展示热门商品(销量 TOP10)、新品上架、限时折扣等推荐列表。 三、订单与交易管理 购物车与下单 消费者可将商品加入购物车,支持修改数量、选择规格、移除商品,系统自动计算总价(含运费、折扣)。 下单流程:确认收货地址→选择支付方式(在线支付、货到付款)→提交订单→系统生成唯一订单号。 订单处理流程 订单状态跟踪:待支付→已支付→商家发货→物流运输→消费者收货→订单完成,各状态变更实时通知用户。 商家端功能:查看新订单提醒、确认发货(填写物流单号)、处理退款申请(需审核理由)。 消费者端功能:查看订单详情、追踪物流、申请退款 / 退货、确认收货。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值