主席树复习

T1 [CQOI2015]任务查询系统

n个任务,每个有运行的时间段和优先级,询问某一时刻,优先级最小的个任务的优先级之和

初做:  2017.2.4   http://www.cnblogs.com/TheRoadToTheGold/p/6366165.html

好像是做了一晚上来

现在:2017.3.27   14:17——15:56

用了接近2个小时做了一道以前做过的题,还是弱啊~~~~(>_<)~~~~

difference:

主席树维护的东西不同,以前直接存储优先级之和,现在存储的是任务个数,其实一个样。。

再就是预处理方式不同

离散化优先级,以时间为下标,以优先级为区间,建立主席树

对主席树理解不透彻,开始离散化优先级,又以优先级为下标,建主席树

离散化了某个值后,它就成了线段树中节点的可控区间范围

个人理解:

主席树中节点有3类属性,下标(自己的、孩子的)、可控区间范围、要维护的值

下标一般存在root[]、lc[]、rc[]中,要维护的值用各种数组存储

在线段树中,可控区间范围是作为节点信息储存的,但主席树一般不存储

在添加或查询时,加上2个参数,表示当前节点的可控区间范围,在递归过程中,范围与当前节点保持一致

代码模板化,所以导致在写的时候

添加节点的pre上来就写root[i-1],这个题是以自身为参照

查询的时候上来就root[i-1],这里不需要参照

一大问题:

这里要保持时间的连续性,若一个任务在[l,r]时间执行

代码中是l位置+1,r位置-1,

所以在添加操作之前,先令root[i]=root[i-1]

目的有二:

1、若某一个时刻在输入中没有出现,这样可以保持时间的连续性

2、添加节点是以自身为参照

开始40分RE,原因:一个任务往主席树中加入2个点,所以数组应*40

#include<cstdio>
#include<algorithm>
#define N 200001
using namespace std;
struct node2
{
    int time,p,w;
}g[N*2];
int hash[N];
int n,m,tot,id;
int root[N*20],lc[N*20],rc[N*20],cnt[N*20];
long long ans=1;
bool cmp(node2 k,node2 q)
{
    return k.time<q.time;
}
void discrete()
{
    sort(hash+1,hash+m+1);
    tot=unique(hash+1,hash+m+1)-hash-1;
    for(int i=1;i<=2*m;i++) g[i].p=lower_bound(hash+1,hash+tot+1,g[i].p)-hash;
}
void insert(int pre,int &now,int l,int r,int pos,int val)
{
    now=++id;
    cnt[now]=cnt[pre]+val;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid)
    {
        rc[now]=rc[pre];
        insert(lc[pre],lc[now],l,mid,pos,val);
    }
    else
    {
        lc[now]=lc[pre];
        insert(rc[pre],rc[now],mid+1,r,pos,val);
    }
}
void query(int now,int l,int r,int k)
{
    if(l==r)
    {
        ans+=1ll*min(cnt[now],k)*hash[l];
        return;
    }
    int mid=l+r>>1,tmp=cnt[lc[now]];
    query(lc[now],l,mid,k);
    if(k>tmp) query(rc[now],mid+1,r,k-tmp);
}
int main()
{
    /*freopen("cqoi15_query.in","r",stdin);
    freopen("cqoi15_query.out","w",stdout);*/
    scanf("%d%d",&m,&n);
    int x,k,a,b,c;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        g[i*2-1].time=a; g[i*2-1].p=c; g[i*2-1].w=1;
        g[i*2].time=b+1; g[i*2].p=c; g[i*2].w=-1;
        hash[i]=c;
    }
    sort(g+1,g+2*m+1,cmp);
    discrete();
    int last=0;
    for(int i=1;i<=2*m;i++)
    {
        for(int j=last+1;j<=g[i].time;j++)
        {
            root[j]=root[j-1];
        }
        insert(root[g[i].time],root[g[i].time],1,tot,g[i].p,g[i].w);
        last=g[i].time;
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d%d",&x,&a,&b,&c);
        k=(ans*a+b)%c+1;ans=0;
        query(root[x],1,tot,k);
        printf("%lld\n",ans);
    }
}

T2  APIO2012 dispatching

每个点有价值、花费,所有的点构成一棵树,给出资金限制

num[i]=在以i为根的子树中,在资金限制内最多能选的点的个数

最大化 s=i的价值*num[i]

初做:2017.2.5 http://www.cnblogs.com/TheRoadToTheGold/p/6368387.html

现在:2017.3.27

16:31做到19:32 中间吃了个饭听了个听力

主席树+dfs序

离散化费用,以费用为区间,以点的dfs序为下标建立主席树

刚开始想的是枚举每个点,然后二分最多能选的点

其实不用二分

用主席树维护到这个点的费用和、点数

然后求在满足费用和<=资金限制的情况下,最多选多少点

很巧妙的是dfs序的应用,但在这儿不是我关注的重点

由于点又有了dfs序,所以一开始就把点的原编号与dfs序搞混了

最大的错误:

查询时,不思考就int mid=l+r>>1,tmp。。。。。。

这里tmp要用long long,卡了1个多小时

模板背熟了是好,注意应用

#include<cstdio>
#include<algorithm>
#define N 100001
using namespace std;
int n,m,se,cnt;
int front[N],nextt[N],to[N],tot_ninja;
int money[N],hashh[N],tot,lead[N];
int in[N],out[N],id[N],s;//id[i]=j :编号为j的是i号忍者
int root[N],lc[N*20],rc[N*20],num[N*20];
long long sum[N*20];
void add(int u,int v)
{
    nextt[++tot_ninja]=front[u]; front[u]=tot_ninja; to[tot_ninja]=v;
}
void dfs(int now)
{
    for(int i=front[now];i;i=nextt[i])
    {
        id[++s]=to[i];
        in[to[i]]=s;
        dfs(to[i]);
    }
    out[now]=s;
}
void insert(int pre,int &now,int l,int r,int pos)
{
    now=++se;
    num[now]=num[pre]+1;
    sum[now]=sum[pre]+hashh[pos];
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid)
    {
        rc[now]=rc[pre];
        insert(lc[pre],lc[now],l,mid,pos);
    }
    else
    {
        lc[now]=lc[pre];
        insert(rc[pre],rc[now],mid+1,r,pos);
    }
}
void discrete()
{
    sort(hashh+1,hashh+n+1);
    tot=unique(hashh+1,hashh+n+1)-hashh-1;
    for(int i=1;i<=n;i++) money[i]=lower_bound(hashh+1,hashh+tot+1,money[i])-hashh;
}
void query(int pre,int now,int l,int r,int limit)
{
    if(l==r)
    {
        cnt+=min(limit/hashh[l],num[now]-num[pre]);
        return;
    }
    int mid=l+r>>1;
    long long tmp=sum[lc[now]]-sum[lc[pre]];
    if(limit<=tmp) query(lc[pre],lc[now],l,mid,limit);
    else
    {
        cnt+=num[lc[now]]-num[lc[pre]];
        query(rc[pre],rc[now],mid+1,r,limit-tmp);
    }
}
int main()
{
    freopen("dispatching.in","r",stdin);
    freopen("dispatching.out","w",stdout);
    scanf("%d%d",&n,&m);
    int x;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&x,&money[i],&lead[i]);
        hashh[i]=money[i];
        add(x,i);
    }
    discrete();
    dfs(0);
    for(int i=1;i<=n;i++) insert(root[i-1],root[i],1,tot,money[id[i]]);
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        cnt=0;
        query(root[in[i]-1],root[out[i]],1,tot,m);
        ans=max(ans,(long long)lead[i]*cnt);
    }
    printf("%lld",ans);
}

时间: 2024-12-28 21:55:33

主席树复习的相关文章

【复习笔记】主席树

昨天在写带修改主席树的时候,咸鱼zcysky发现自己似乎根本不会写主席树 于是正好找个空复习下-- 主席树的原理不用我扯了,主席树为啥能求k大,大概在它可以用历史版本存下区间的前缀和,求的时候差分下就能提出我要求的区间. 不过这么搞的话不要忘了离散化. 1.kth number 就是上面的裸题,不要手贱写bits就好. 1 #include<iostream> 2 #include<cstdio> 3 #define N 100010 4 #include<algorithm

蒟蒻林荫的小复习——主席树

主席树也就是指可持久化线段树,大致思想也就是每次利用之前的重复信息,只为被更新的一部分开辟新点.而且所谓可持久化线段树实际上是指可持久化权值线段树,线段树中每个端点存的是这个端点所代表的树的出现次数. 而在主席树的维护当中对于每个历史版本如果都开一颗新树,那么M将是最终的结局.正确解法则是为每一个改变的点分配编号而未改变的点直接链接到新树上. 下面就来从主席树的两个经典问题来考虑 1:区间第K小问题 传送门 首先分析问题,给出一个序列,每次给定一个封闭区间,求这个封闭区间以内的最小值. 上面我们

bzoj 4026 dC Loves Number Theory (主席树+数论+欧拉函数)

题目大意:给你一个序列,求出指定区间的(l<=i<=r) mod 1000777 的值 还复习了欧拉函数以及线性筛逆元 考虑欧拉函数的的性质,(l<=i<=r),等价于 (p[j]是区间内所有出现过的质数) 那么考虑找出区间内所有出现过的质数,这思路和HH的项链是不是很像?? 由于此题强制在线,所以把树状数组替换成了主席树而已 原来我以前写的主席树一直都是错的......还好推出了我原来错误代码的反例 在继承上一个树的信息时,注意不要破坏现在的树 1 #include <cs

hdu6704 后缀数组+主席树+ST +二分

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6704 推荐博客:https://www.cnblogs.com/1625--H/p/11403199.html 题意:输入t,接下来t组数据,每组数据输入n和q,n代表字符串长度,q代表查询次数,接下来一行输入长度为n的字符串,最后再输入q行查询,每一个查询有l,r,k,查询字符串中在区间[l,r]里的这个子串第k次出现的位置,位置就是出现这个子串的首字母位置,如果没有第k次,则输出-1. 比赛时我

[poj2104]可持久化线段树入门题(主席树)

解题关键:离线求区间第k小,主席树的经典裸题: 对主席树的理解:主席树维护的是一段序列中某个数字出现的次数,所以需要预先离散化,最好使用vector的erase和unique函数,很方便:如果求整段序列的第k小,我们会想到离散化二分和线段树的做法, 而主席树只是保存了序列的前缀和,排序之后,对序列的前缀分别做线段树,具有差分的性质,因此可以求任意区间的第k小,如果主席树维护索引,只需要求出某个数字在主席树中的位置,即为sort之后v中的索引:若要求第k大,建树时反向排序即可 1 #include

【BZOJ 3551】[ONTAK2010] Peaks加强版 Kruskal重构树+树上倍增+主席树

这题真刺激...... I.关于Kruskal重构树,我只能开门了,不过补充一下那玩意还是一棵满二叉树.(看一下内容之前请先进门坐一坐) II.原来只是用树上倍增求Lca,但其实树上倍增是一种方法,Lca只是他的一种应用,他可以搞各种树上问题,树上倍增一般都会用到f数组. |||.我们跑出来dfs序就能在他的上面进行主席树了. IV.别忘了离散. V.他可能不连通,我一开始想到了,但是我觉得出题人可能会是好(S)人(B),但是...... #include <cstdio> #include

[bzoj3932][CQOI2015]任务查询系统-题解[主席树][权值线段树]

Description 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行 ),其优先级为Pi.同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同.调度系统会经常向 查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个 )的优先级之和是多少.特别的,如

BZOJ_3207_花神的嘲讽计划1_(Hash+主席树)

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=3207 给出一个长度为\(n\)的串,以及\(m\)个长度为\(k\)的串,求每个长度为\(k\)的串在原串\([x,y]\)区间是否出现过. 分析 这道题要求对比长度为\(k\)的串,于是我们把这些串的Hash值都算出来,问题就转化成了求\([x,y]\)的区间中是否出现过某Hash值. 求区间中某一个值出现了多少次,可以用主席树. p.s. 1.学习了主席树指针的写法,比数组慢好多啊...

[主席树]ZOJ3888 Twelves Monkeys

题意:有n年,其中m年可以乘时光机回到过去,q个询问 下面m行,x,y 表示可以在y年穿越回x年, 保证y>x 下面q个询问, 每个询问有个年份k 问的是k年前面 有多少年可以通过一种以上($\ge 2$)方法穿越回去的, 其中时光机只能用一次 比如案例 9 3 3 9 1 6 1 4 1 6 7 2 如图 对于询问 6这一年:1.穿越回第1年  2.等时间过呀过呀过到第9年,再穿越回第1年 那么第1年就有两种方法可以穿越回去, 同理, 2.3.4年也有同样两种方法(回到1再等时间过呀过 过到2