线段树的应用

来源:互联网 发布:索尼网络签约经销商 编辑:程序博客网 时间:2024/03/29 00:04

线段树在信息学竞赛中是一种十分优秀的数据结构,具有维护区间信息,查询等操作,更有区间合并的种种的巧妙地性质,除了递归常数外,其复杂度是稳定在log 的。
蒟蒻初来乍到YL信息组的时候lg就开始教我们线段树,zkw线段树,主席树,划分树等神树,当时语言都没完全掌握好,自然听起来一头雾水。所以本人负责的建议,线段树学习还是在掌握好搜索和递归之后再来学,不然只能理解个大概,许多细节会理解不了,题目一变就不知所措了。
最近在看hzw学长的blog,上面有许多线段树好题,kuangbin的带你飞系列也有很多线段树的好题,都可以去做做,链接在这
hzwer kuangbin

注意!!!!

本文着重讲线段树的应用,从基础到稍微难一点的题,如果基础都不知道的话请进入下面的blog好好理解了再来看看作者的blog
初学1
初学2
下面开始进入正题

线段树的查询与修改

敌兵布阵
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:”你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:”我知错了。。。”但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.
Input
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令
Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

这是一道线段树的单点修改和区间查询的裸题,是道入门模板题,线段树的单点修改就是一直递归到叶子节点把logn个节点的值修改,回溯的时候往上维护信息即可。线段树的区间查询也不是很难,我们考虑三种情况
1.当前查询的左端点小于等于当前节点储存的中点,那我们继续往左儿子找就好了。
2.当前查询的右端点大于中点,那我们往右儿子找。
3.如果当前询问的左右端点包含了中点,那我们就把询问区间变为询问区间左端点到中点,中点+1到询问右端点两个区间然后递归查找就好了。

/*************************************************************************    > File Name: 敌兵布阵.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/8/24 23:26:23************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 300000+10;int T,n,a[maxn];int tr[maxn];void build(int h,int l,int r){    if(l == r){tr[h] = a[l];return ;}    int mid = (l + r) >> 1;    build(h<<1,l,mid);build(h<<1|1,mid+1,r);    tr[h] = tr[h<<1] + tr[h<<1|1];}void updata(int h,int l,int r,int p,int x){    if(l == r){tr[h] += x;return ;}    int mid = (l + r) >> 1;    if(p <= mid)updata(h<<1,l,mid,p,x);    else updata(h<<1|1,mid+1,r,p,x);    tr[h] = tr[h<<1] + tr[h<<1|1];}int query(int h,int l,int r,int q,int w){    if(l == q && r == w)return tr[h];    int mid = (l + r) >> 1;    if(w <= mid)return query(h<<1,l,mid,q,w);    else if(q > mid)return query(h<<1|1,mid+1,r,q,w);    else return query(h<<1,l,mid,q,mid)+query(h<<1|1,mid+1,r,mid+1,w);}//三个分类讨论的情况,q为询问左端点,w为右端点char s[10];int main(){    T = read();    REP(I,1,T)    {        n = read();        REP(i,1,n) a[i] = read();        build(1,1,n);        cout<<"Case "<<I<<":"<<endl;        while(scanf("%s",s) && s[0]!='E')        {            if(s[0] == 'Q')            {                int l = read(),r = read();                cout<<query(1,1,n,l,r)<<endl;            }            if(s[0] == 'A')            {                int x = read(),y = read();                updata(1,1,n,x,y);            }            if(s[0] == 'S')            {                               int x = read(),y = read();                updata(1,1,n,x,-y);            }        }    }    return 0;}

I Hate It

很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。
这让很多学生很反感。
不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。
Input
本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N 和 M ( 0 < N <= 200000 ,0 < M<5000 ),分别代表学生的数目和操作的数目。
学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取’Q’或’U’) ,和两个正整数A,B。
当C为’Q’的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为’U’的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
Output
对于每一次询问操作,在一行里面输出最高成绩。

这里就是我所说的线段树的区间合并的优秀的性质,我们发现我们要维护一段区间的最大值可以转变为两个子问题,将它分为两个区间,答案是两个子区间的最大值取max,然后我们一直递归下去,一直到叶子节点。单点修改就是从叶子节点一直回溯,边回溯边更新答案。

/*************************************************************************    > File Name: IHATEIT.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/8/24 23:38:09************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 1000000+10;int tr[maxn];int n,m;int a[maxn];void build(int h,int l,int r){    if(l == r){tr[h] = a[l];return ;}    int mid = (l + r) >> 1;    build(h<<1,l,mid);build(h<<1|1,mid+1,r);    tr[h] = max(tr[h<<1],tr[h<<1|1]);}char s[30];void updata(int h,int l,int r,int p,int x){    if(l == r){tr[h] = x;return ;}    int mid = (l + r) >> 1;    if(p <= mid)updata(h<<1,l,mid,p,x);    else updata(h<<1|1,mid+1,r,p,x);    tr[h] = max(tr[h<<1],tr[h<<1|1]);}int query(int h,int l,int r,int q,int w){    if(l == q && r == w)return tr[h];    int mid = (l + r) >> 1;    if(w <= mid)return query(h<<1,l,mid,q,w);    else if(q > mid)return query(h<<1|1,mid+1,r,q,w);    else return max(query(h<<1,l,mid,q,mid),query(h<<1|1,mid+1,r,mid+1,w));}int main(){    while(scanf("%d%d",&n,&m)!=EOF)    {        REP(i,1,n) a[i] = read();        build(1,1,n);        REP(I,1,m)        {            cin>>s;            if(s[0] =='Q')            {                int l = read(),r = read();                cout<<query(1,1,n,l,r)<<endl;            }            if(s[0] == 'U')            {                int x = read(),y = read();                updata(1,1,n,x,y);            }        }    }    return 0;}

线段树的区间更新

说白了,区间更新和单点更新也没什么很大的区别,只是一个是单点,一个是区间,然而我们每次都只更新一个点的话,复杂度高达O(nlog2n),显然我们是承受不起的,于是我们有了lazy_tag的思想,这个是什么意思呢?我们在没有访问到这个节点时,并不需要对这个节点更新,只需要维护一下它祖先的信息即可,当程序访问到这个节点还要往他的子孙访问时,就一定要更新子树信息,把这个标记下传,等价于说,你没访问这个节点,我只需要保证答案正确性,把区间维护的值改一下,不需要递归到更深的节点,当我们访问到了这个节点,我们就需要动一动这个标记,把他传给两个儿子就好了,这样复杂度就没问题了。

A Simple Problem with Integers
You have N integers, A1, A2, … , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
Input
The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, … , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
“C a b c” means adding c to each of Aa, Aa+1, … , Ab. -10000 ≤ c ≤ 10000.
“Q a b” means querying the sum of Aa, Aa+1, … , Ab.
Output
You need to answer all Q commands in order. One answer in a line.

/*************************************************************************    > File Name: C.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/8/24 23:48:56************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register LL i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register LL i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))typedef long long LL;LL read(){    LL sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 8000000+10;LL n,m;LL a[maxn],tr[maxn],tag[maxn];char s[30];void build(LL h,LL l,LL r){    tag[h] = 0;    if(l == r){tr[h] = a[l];return ;}    LL mid = (l + r) >> 1;    build(h<<1,l,mid);build(h<<1|1,mid+1,r);    tr[h] = tr[h<<1] + tr[h<<1|1];}void pushdown(LL h,LL l,LL r){    LL mid = (l + r) >> 1;    tr[h << 1] += (mid - l + 1) * tag[h];    tr[h << 1|1] += (r - mid) * tag[h];    tag[h<<1] += tag[h];tag[h<<1|1] += tag[h];    tag[h] = 0;}void updata(LL h,LL l,LL r,LL q,LL w,LL z){    if(l == q && r == w)    {        tag[h] += z;        tr[h] += (r - l + 1) * z;        return ;    }    if(tag[h])pushdown(h,l,r);    LL mid = (l + r) >> 1;    if(w <= mid)updata(h<<1,l,mid,q,w,z);    else if(q > mid)updata(h<<1|1,mid+1,r,q,w,z);    else updata(h<<1,l,mid,q,mid,z),updata(h<<1|1,mid+1,r,mid+1,w,z);    tr[h] = tr[h<<1] + tr[h<<1|1];}LL query(LL h,LL l,LL r,LL q,LL w){    if(q == l && r == w)return tr[h];    if(tag[h])pushdown(h,l,r);    LL mid = (l + r) >> 1;    if(w <= mid)return query(h<<1,l,mid,q,w);    else if(q > mid)return query(h<<1|1,mid+1,r,q,w);    else return query(h<<1,l,mid,q,mid) + query(h<<1|1,mid+1,r,mid+1,w);}int main(){    n = read(),m = read();    REP(i,1,n) a[i] = read();    build(1,1,n);    REP(I,1,m)    {        cin>>s;        if(s[0] == 'Q')        {            LL x = read(),y = read();            cout<<query(1,1,n,x,y)<<endl;        }        else        {            LL x = read(),y = read();            LL z = read();            updata(1,1,n,x,y,z);        }    }    return 0;}

以上都是常规操作,是必须要掌握的,接下来讲一些线段树的奇妙的应用,都十分优秀与神奇。

线段树动态维护区间最大连续字段和

【vijos1083】小白逛公园

描述
小新经常陪小白去公园玩,也就是所谓的遛狗啦…在小新家附近有一条“公园路”,路的一边从南到北依次排着n个公园,小白早就看花了眼,自己也不清楚该去哪些公园玩了。
一开始,小白就根据公园的风景给每个公园打了分-.-。小新为了省事,每次遛狗的时候都会事先规定一个范围,小白只可以选择第a个和第b个公园之间(包括a、b两个公园)选择连续的一些公园玩。小白当然希望选出的公园的分数总和尽量高咯。同时,由于一些公园的景观会有所改变,所以,小白的打分也可能会有一些变化。
那么,就请你来帮小白选择公园吧。
输入格式
第一行,两个整数N和M,分别表示表示公园的数量和操作(遛狗或者改变打分)总数。
接下来N行,每行一个整数,依次给出小白 开始时对公园的打分。
接下来M行,每行三个整数。第一个整数K,1或2。K=1表示,小新要带小白出去玩,接下来的两个整数a和b给出了选择公园的范围(1≤a,b≤N, a可以大于b!);K=2表示,小白改变了对某个公园的打分,接下来的两个整数p和s,表示小白对第p个公园的打分变成了s(1≤p≤N)。
其中,1≤N≤500 000,1≤M≤100 000,所有打分都是绝对值不超过1000的整数。
输出格式
小白每出去玩一次,都对应输出一行,只包含一个整数,表示小白可以选出的公园得分和的最大值。

这道题刘汝佳蓝书上也讲过,我们维护三个标记,从区间左端点向右的最大连续和(lmax),从区间右端点向左的最大连续和(rmax),这个区间的最大连续和(hmax),这样我们每次把一个区间合并就这么更新,tr为区间和,在纸上画一下就懂了。

tr[h] = tr[h << 1] + tr[h << 1|1];lmax[h]= max(lmax[h<<1],tr[h<<1]+lmax[h<<1|1]);rmax[h]=max(rmax[h<<1|1],tr[h<<1|1]+rmax[h<<1]; hmax[h]=max(max(hmax[h<<1],hmax[h<<1|1]),lmax[h<<1|1]+rmax[h<<1]);
/*************************************************************************    > File Name: vijos1083.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/8/22 23:14:38************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 500000+10;const int maxm = 2000000+10;int tr[maxm],lmax[maxm],rmax[maxm],hmax[maxm];int a[maxn],n,m;void push_up(int h){    tr[h] = tr[h << 1] + tr[h << 1|1];    lmax[h] = max(lmax[h<<1],tr[h<<1]+lmax[h<<1|1]);    rmax[h] = max(rmax[h<<1|1],tr[h<<1|1]+rmax[h<<1]);    hmax[h] = max(max(hmax[h<<1],hmax[h<<1|1]),lmax[h<<1|1]+rmax[h<<1]);}void build(int h,int l,int r){    if(l == r)    {        tr[h] = lmax[h] = rmax[h] = hmax[h] = a[l];        return ;    }    int mid = (l + r) >> 1;    build(h<<1,l,mid);build(h<<1|1,mid+1,r);    push_up(h);}void updata(int h,int l,int r,int p,int s){    if(l == r)    {        tr[h] = lmax[h] = rmax[h] = hmax[h] = s;        return ;    }    int mid = (l + r) >> 1;    if(p <= mid)updata(h<<1,l,mid,p,s);    else updata(h<<1|1,mid+1,r,p,s);    push_up(h);}struct T{    int lm,rm,ans,sum;};T query(int h,int l,int r,int q,int w){    if(l == q && r == w)    {        T ls;        ls = (T){lmax[h],rmax[h],hmax[h],tr[h]};        return ls;    }    int mid = (l + r) >> 1;    if(w <= mid)return query(h<<1,l,mid,q,w);    else if(q > mid)return query(h<<1|1,mid+1,r,q,w);    T a,b,c;b = query(h<<1,l,mid,q,mid);c = query(h<<1|1,mid+1,r,mid+1,w);    a.ans = max(max(b.ans,c.ans),b.rm+c.lm);    a.rm = max(c.sum+b.rm,c.rm);    a.lm = max(b.sum+c.lm,b.lm);    return a;}int main(){    n = read();m = read();    REP(i,1,n) a[i] = read();    build(1,1,n);    REP(i,1,m)    {        int k = read();        if(k == 1)        {            int a = read(),b = read();            if(a > b)swap(a,b);            cout<<query(1,1,n,a,b).ans<<endl;        }        else        {            int p = read(),s = read();            updata(1,1,n,p,s);        }    }    return 0;}

区间赋零

NOI2004郁闷的出纳员

输入描述 Input Description
第一行有两个非负整数n和min。n表示下面有多少条命令,min表示工资下界。
接下来的n行,每行表示一条命令。命令可以是以下四种之一:
名称 格式 作用
I命令 I_k 新建一个工资档案,初始工资为k。如果某员工的初始工资低于工资下界,他将立刻离开公司。
A命令 A_k 把每位员工的工资加上k
S命令 S_k 把每位员工的工资扣除k
F命令 F_k 查询第k多的工资
_(下划线)表示一个空格,I命令、A命令、S命令中的k是一个非负整数,F命令中的k是一个正整数。
在初始时,可以认为公司里一个员工也没有。
输出描述 Output Description
输出文件的行数为F命令的条数加一。
对于每条F命令,你的程序要输出一行,仅包含一个整数,为当前工资第k多的员工所拿的工资数,如果k大于目前员工的数目,则输出-1。
输出文件的最后一行包含一个整数,为离开公司的员工的总数。

这道题实际上好像是splay的一道裸题,但是线段树可以做,我们建一颗权值线段树,然后用一个变量来记录工资加减的变化,但是可能存在<0的情况于是我们每个位置都加上200000防止变负数,然后工资小于k的就相当于是把0到k的区间变为零,我们只需要用一个tag就好了,不用更新叶子结点,遇到这个tag把区间和他的两个儿子变为0就好了,第k大直接主席树的方法就好了。

/*************************************************************************    > File Name: bzoj1503.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/8/20 23:43:01************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 400000+10;int n,Min;int tree[maxn*10+10];char s[10];void updata(int h,int l,int r,int p){    if(l!=r && tree[h] == 0)tree[h<<1] = tree[h<<1|1] = 0;    ++tree[h];    if(l == r)return ;    int mid = (l + r) >> 1;    if(p <= mid) updata(h<<1,l,mid,p);    else updata(h<<1|1,mid+1,r,p);}int ans = 0;void del(int h,int l,int r,int p){    if(tree[h] == 0)tree[h<<1] = tree[h<<1|1] = 0;    if(r <= p){ans += tree[h];tree[h] = 0;return ;}    int mid =  (l + r) >> 1;    del(h<<1,l,mid,p);    if(mid < p)del(h<<1|1,mid+1,r,p);    tree[h] = tree[h<<1] + tree[h<<1|1];}int query(int h,int l,int r,int k){    if(tree[h] == 0)tree[h<<1] = tree[h<<1|1] = 0;    if(l == r)return l;    int mid = (l + r) >> 1;    if(tree[h<<1] >= k)return query(h<<1,l,mid,k);    else return query(h<<1|1,mid+1,r,k-tree[h<<1]);}int main(){    n = read(),Min = read();    int change = 0;Min+=200000;    REP(i,1,n)    {        cin>>s;int x = read();        if(s[0] == 'I')        {            x += 200000;            if(x>=Min)updata(1,1,maxn,x-change);        }        if(s[0] == 'A')change += x;        if(s[0] == 'S')change -= x,del(1,1,maxn,Min-change-1);        if(s[0] == 'F')        {            x = tree[1] - x + 1;            if(x < 1)puts("-1");            else cout<<query(1,1,maxn,x)+change-200000<<endl;        }    }    cout<<ans<<endl;    return 0;}

维护询问信息

【tyvj1473】校门外的树3

校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的……
如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作:
K=1,读入l,r表示在l~r之间种上的一种树
K=2,读入l,r表示询问l~r之间能见到多少种树
(l,r>0)
输入格式 InputFormat
第一行n,m表示道路总长为n,共有m个操作
接下来m行为m个操作
输出格式 OutputFormat
对于每个k=2输出一个答案

这道题我一开始是不会做的,看了网上很多blog才看懂的。我们这样考虑,对于一个询问区间l,r对这个区间产生贡献的树是不是就是种树的那个区间的右端点大于l并且,种树的区间左端点小于r,于是我们只需要统计右端点有多少在l-r间减去左端点在l+1-n之间的就好了。

/*************************************************************************    > File Name: tyvj1473.cpp    > Author: Drinkwater-cnyali    > Created Time: 2017/8/22 0:03:26************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>using namespace std;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))int read(){    int sum = 0, fg = 1; char c = getchar();    while(c < '0' || c > '9') { if (c == '-') fg = -1; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return sum * fg;}const int maxn = 100000+10;int n,m,tr1[maxn<<2],tr2[maxn<<2];void add1(int h,int l,int r,int p){    if(l == r){tr1[h]++;return ;}    int mid = (l + r) >> 1;    if(p <= mid)add1(h<<1,l,mid,p);    else add1(h<<1|1,mid+1,r,p);    tr1[h] = tr1[h<<1] + tr1[h<<1|1];}void add2(int h,int l,int r,int p){    if(l == r){tr2[h]++;return ;}    int mid = (l + r) >> 1;    if(p <= mid)add2(h<<1,l,mid,p);    else add2(h<<1|1,mid+1,r,p);    tr2[h] = tr2[h<<1] + tr2[h<<1|1];}int query1(int h,int l,int r,int q,int w){    if(l == q && r == w)return tr1[h];    int mid = (l + r) >> 1;    if(w <= mid)return query1(h<<1,l,mid,q,w);    else if(q > mid)return query1(h<<1|1,mid+1,r,q,w);    else return query1(h<<1,l,mid,q,mid)+query1(h<<1|1,mid+1,r,mid+1,w);}int query2(int h,int l,int r,int q,int w){    if(l == q && r == w)return tr2[h];    int mid = (l + r) >> 1;    if(w <= mid)return query2(h<<1,l,mid,q,w);    else if(q > mid)return query2(h<<1|1,mid+1,r,q,w);    else return query2(h<<1,l,mid,q,mid)+query2(h<<1|1,mid+1,r,mid+1,w);}int main(){    n = read(), m = read();    REP(i,1,m)    {        int k = read();        if(k == 1)        {            int l = read(),r = read();            add1(1,1,n,r);add2(1,1,n,l);        }        else        {            int l = read(),r = read();            cout<<query1(1,1,n,l,n)-query2(1,1,n,r+1>n?n:r+1,n)<<endl;        }    }    return 0;}

巧妙的性质

【bzoj3038】上帝造题的七分钟2
XLk觉得《上帝造题的七分钟》不太过瘾,于是有了第二部。
“第一分钟,X说,要有数列,于是便给定了一个正整数数列。
第二分钟,L说,要能修改,于是便有了对一段数中每个数都开平方(下取整)的操作。
第三分钟,k说,要能查询,于是便有了求一段数的和的操作。
第四分钟,彩虹喵说,要是NOIP难度,于是便有了数据范围。
第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。
第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过64位有符号整数类型的表示范围的限制。
第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。”
——《上帝造题的七分钟·第二部》
所以这个神圣的任务就交给你了。

这道题在bzoj上好像是一道权限题,我就没做了,但是这道题巧妙就是在单点更新的优化上。因为是平方和所以一个数被开5次就很小了,所以当一个数变成0/1我们就不用管了,这个优化可以大大节省我们的时间。

tag的另一种打法

codevs1690

题目描述 Description
YYX家门前的街上有N(2<=N<=100000)盏路灯,在晚上六点之前,这些路灯全是关着的,六点之后,会有M(2<=m<=100000)个人陆续按下开关,这些开关可以改变从第i盏灯到第j盏灯的状态,现在YYX想知道,从第x盏灯到第y盏灯中有多少是亮着的(1<=i,j,x,y<=N)
输入描述 Input Description
第 1 行: 用空格隔开的两个整数N和M
第 2..M+1 行: 每行表示一个操作, 有三个用空格分开的整数: 指令号(0代表按下开关,1代表询问状态), x 和 y
输出描述 Output Description
第 1..询问总次数 行:对于每一次询问,输出询问的结果

这道题的tag就像二进制一样,访问了偶数次是不变的,于是tag每次^1一下,遇到tag为1的才更新

/*************************************************************************     > Author: Drinkwater     > Created Time: 2017/8/23 21:24:59 ************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>#prag\ma GCC optimize("O3")using namespace std;typedef long long LL;typedef unsigned long long uLL;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }int read(){    register int sum = 0,fg = 0;char c = getchar();    while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return fg ? -sum : sum;}const int inf = 1e9;const int maxn = 500000;int n,m;int tr[maxn],tag[maxn];void change(int h,int l,int r){    tr[h] = r - l + 1 - tr[h];    tag[h] = tag[h] ^ 1;}void pushdown(int h,int l,int r){    int mid = (l + r) >> 1;    change(h<<1,l,mid);    change(h<<1|1,mid+1,r);    tag[h] = 0;}void updata(int h,int l,int r,int q,int w){    if(l == q && r == w)    {        change(h,l,r);        return ;    }    if(tag[h])pushdown(h,l,r);    int mid = (l + r ) >> 1;    if(w <= mid)updata(h<<1,l,mid,q,w);    else if(q > mid)updata(h<<1|1,mid+1,r,q,w);    else updata(h<<1,l,mid,q,mid),updata(h<<1|1,mid+1,r,mid+1,w);    tr[h] = tr[h<<1] + tr[h<<1|1];}int query(int h,int l,int r,int q,int w){    if(l == q && r == w)return tr[h];    if(tag[h])pushdown(h,l,r);    int mid = (l + r) >> 1;    if(w <= mid)return query(h<<1,l,mid,q,w);    else if(q > mid)return query(h<<1|1,mid+1,r,q,w);    else return query(h<<1,l,mid,q,mid) + query(h<<1|1,mid+1,r,mid+1,w);}int main(){    n = read(),m = read();    REP(i,1,m)    {        int k = read();        int l = read(),r = read();        if(k == 0)updata(1,1,n,l,r);        else cout<<query(1,1,n,l,r)<<endl;    }    return 0;}

dfs序加线段树

做题过程中,我们常常会遇见dfs序和线段树在一起,因为有了dfs序,我们就可以把树变得线性,可以维护子树的信息了。

HDU5692

Problem Description
百度科技园内有n个零食机,零食机之间通过n−1条路相互连通。每个零食机都有一个值v,表示为小度熊提供零食的价值。
由于零食被频繁的消耗和补充,零食机的价值v会时常发生变化。小度熊只能从编号为0的零食机出发,并且每个零食机至多经过一次。另外,小度熊会对某个零食机的零食有所偏爱,要求路线上必须有那个零食机。
为小度熊规划一个路线,使得路线上的价值总和最大。
Input
输入数据第一行是一个整数T(T≤10),表示有T组测试数据。
对于每组数据,包含两个整数n,m(1≤n,m≤100000),表示有n个零食机,m次操作。
接下来n−1行,每行两个整数x和y(0 ≤ x,y < n),表示编号为x的零食机与编号为y的零食机相连。
接下来一行由n个数组成,表示从编号为0到编号为n−1的零食机的初始价值v(|v|<100000)。
接下来m行,有两种操作:0 x y,表示编号为x的零食机的价值变为y;1 x,表示询问从编号为0的零食机出发,必须经过编号为x零食机的路线中,价值总和的最大值。
本题可能栈溢出,辛苦同学们提交语言选择c++,并在代码的第一行加上:
#pragma comment(linker, "/STACK:1024000000,1024000000")
Output
对于每组数据,首先输出一行”Case #?:”,在问号处应填入当前数据的组数,组数从1开始计算。
对于每次询问,输出从编号为0的零食机出发,必须经过编号为x零食机的路线中,价值总和的最大值。

这道题就是一道dfs序的裸题,熟悉一下性质就好了。

/*************************************************************************     > Author: Drinkwater     > Created Time: 2017/8/24 18:52:38 ************************************************************************/#pragma comment(linker, "/STACK:1024000000,1024000000")#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>#prag\ma GCC optimize("O3")using namespace std;typedef long long LL;typedef unsigned long long uLL;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }LL read(){    register LL sum = 0,fg = 0;char c = getchar();    while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return fg ? -sum : sum;}const int inf = 1e9;const int maxn = 400000;LL T,n,m,w[maxn];LL dis[maxn],size[maxn],dfn[maxn],cnt,tid[maxn];LL be[maxn],ne[maxn],to[maxn],e;void add(int x,int y){    to[++e] = y;ne[e] = be[x];be[x] = e;}   void dfs(int x,int fa){    size[x] = 1;dfn[x] = ++cnt;tid[cnt] = x;    for(int i = be[x]; i; i = ne[i])    {        int v = to[i];        if(v != fa)        {            dis[v] = dis[x] + w[v];            dfs(v,x);size[x] += size[v];        }    }}LL tr[maxn<<2],tag[maxn<<2];void build(int h,int l,int r){    tag[h] = 0;    if(l == r) { tr[h] = dis[tid[l]];return ;}     int mid = (l + r) >> 1;    build(h<<1,l,mid);build(h<<1|1,mid+1,r);    tr[h] = max(tr[h<<1] , tr[h<<1|1]);}void pushdown(int h){    tag[h<<1] += tag[h];tag[h<<1|1] += tag[h];    tr[h<<1] += tag[h];tr[h<<1|1] += tag[h];    tag[h] = 0;}void updata(int h,int l,int r,int q,int w,int e){    if(l == q && r == w)    {        tag[h] += e;tr[h] += e;        return ;    }       if(tag[h])pushdown(h);    int mid = (l + r) >> 1;    if(w <= mid)updata(h<<1,l,mid,q,w,e);    else if(q > mid)updata(h<<1|1,mid+1,r,q,w,e);    else updata(h<<1,l,mid,q,mid,e),updata(h<<1|1,mid+1,r,mid+1,w,e);    tr[h] = max(tr[h<<1],tr[h<<1|1]);}LL query(int h,int l,int r,int q,int w){    if(l == q && r == w)return tr[h];    if(tag[h])pushdown(h);    int mid = (l + r) >> 1;    if(w <= mid)return query(h<<1,l,mid,q,w);    else if(q > mid)return query(h<<1|1,mid+1,r,q,w);    else return max(query(h<<1,l,mid,q,mid),query(h<<1|1,mid+1,r,mid+1,w));}int main(){    //freopen("hdu5692.in", "r", stdin);    //freopen("hdu5692.out", "w", stdout);    T = read();    REP(I,1,T)    {        mem(be,0);e = 0;        n = read();m = read();        REP(i,1,n-1)        {            int x = read(),y = read();x++,y++;            add(x,y);add(y,x);        }        REP(i,1,n)w[i] = read();        mem(dis,0);mem(size,0);mem(dfn,0);        cnt = 0;dis[1] = w[1];dfs(1,1);        build(1,1,n);        cout<<"Case #"<<I<<":"<<endl;        while(m--)        {            int k = read();            if(k == 0)            {                int x = read();                LL y = read();x++;                int l = dfn[x],r = l + size[x] - 1;                updata(1,1,n,l,r,y-w[x]);w[x] = y;            }            else            {                int x = read();x++;                cout<<query(1,1,n,dfn[x],dfn[x]+size[x]-1)<<endl;            }        }    }    return 0;}

扫描线加线段树

这猫是线段树和其他结合的最好的,当然还有树剖啥的。
这个内容去看看下面的blog看看就懂了

大神的blog

HDU1542线段树求矩形面积并

/*************************************************************************     > Author: Drinkwater     > Created Time: 2017/8/23 19:12:01 ************************************************************************/#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<cstring>#include<algorithm>#prag\ma GCC optimize("O3")using namespace std;typedef long long LL;typedef unsigned long long uLL;#define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)#define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)#define mem(a, b) memset((a), b, sizeof(a))template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }int read(){    register int sum = 0,fg = 0;char c = getchar();    while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }    while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }    return fg ? -sum : sum;}const int inf = 1e9;const int maxn = 600000+10;int n,cnt,num,k;struct T{    double l,r,h;int c;    bool operator < (const T&u)const    {        return h < u.h;    }}line[maxn];double X[maxn];int tr[maxn];double sm[maxn];void pushup(int h){    if(tr[h<<1] == -1 || tr[h<<1|1] == -1)        tr[h] = -1;    else if(tr[h<<1]!=tr[h<<1|1])        tr[h] = -1;    else tr[h] = tr[h<<1];    sm[h] = sm[h<<1] + sm[h<<1|1];}void build(int h,int l,int r){    if(l == r){tr[h] = 0;return ;}    int mid = (l + r) >> 1;    build(h<<1,l,mid);build(h<<1|1,mid+1,r);    tr[h] = tr[h<<1] + tr[h<<1|1];}void pushdown(int h,int l,int r){    int mid = (l + r) >> 1;    if(tr[h] != -1)    {        tr[h<<1] = tr[h<<1|1] = tr[h];        sm[h<<1] = (tr[h] ? X[mid+1]-X[l] : 0);        sm[h<<1|1] = (tr[h] ? X[r+1]-X[mid+1] : 0);    }}void updata(int h,int l,int r,int q,int w,int e){    if(q == l && r == w)    {        if(tr[h]!=-1)        {            tr[h]+=e;sm[h] = (tr[h] ? X[r+1] - X[l] : 0);            return ;        }    }    pushdown(h,l,r);    int mid = (l + r) >> 1;    if(w <= mid)updata(h<<1,l,mid,q,w,e);    else if(q > mid)updata(h<<1|1,mid+1,r,q,w,e);    else        updata(h<<1,l,mid,q,mid,e),updata(h<<1|1,mid+1,r,mid+1,w,e);    pushup(h);}int main(){    int cs = 0;    while(scanf("%d",&n) && n)    {        double a1,b1,a2,b2;        cnt = num = k = 0;        REP(i,1,n)        {            scanf("%lf%lf%lf%lf",&a1,&b1,&a2,&b2);            line[++cnt] = (T){a1,a2,b1,1};            X[++num] = a1;            line[++cnt] = (T){a1,a2,b2,-1};            X[++num] = a2;        }        sort(X+1,X+1+num);        sort(line+1,line+1+cnt);        REP(i,1,num)            if(X[i]!=X[i-1])X[++k] = X[i];        build(1,0,k);        double res = 0;        cout<<"Test case #"<<++cs<<endl;        REP(i,1,cnt-1)        {            int l = lower_bound(X+1,X+1+k,line[i].l)-X;            int r = lower_bound(X+1,X+1+k,line[i].r)-X;r--;            updata(1,0,k,l,r,line[i].c);            res += sm[1] * (line[i+1].h-line[i].h);        }        cout<<"Total explored area: ";        printf("%.2lf\n",res);    }    return 0;}
原创粉丝点击