线段树的应用
线段树在信息学竞赛中是一种十分优秀的数据结构,具有维护区间信息,查询等操作,更有区间合并的种种的巧妙地性质,除了递归常数外,其复杂度是稳定在
蒟蒻初来乍到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;
}
线段树的区间更新
说白了,区间更新和单点更新也没什么很大的区别,只是一个是单点,一个是区间,然而我们每次都只更新一个点的话,复杂度高达
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看看就懂了
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;
}