http://www.notonlysuccess.com/?p=59
这几天陆陆续续更新了下边几道我所能找到得具有一些代表性的线段树题目
从最最简单的区间求和到对区间的各种操作都包涵在这些题目里了
相信对一些准备学习线段树的人有一定得帮助
突然发现自己对数据结构的题目非常有感觉,所以在刷下边的题的同时也生出灵感出了好几道线段树题目
等比赛结束后也会陆续加进里边
快半年过去代码风格也有很大的改变,感觉以前写的代码很不规范,用自己在预定义中定义的一些函数,但后来感觉作用不是很大,所以又删去了,所以现在看代码可能找不到以前我定义的一些函数,本来想更新一下的,无奈这些函数用的太多,改之太过麻烦,所以在此处申明一下
#define LL(x) ((x)<<1)
#define RR(x) ((x)<<1|1)
#define FF(i,n) for(int i = 0 ; i < n ; i ++)
若还有不清楚的地方,只管提出来便是,我一定一一改正
1.hdu1166 敌兵布阵
更新节点,区间求和。一次AC,做的时候也没遇见什么问题!
#include #include using namespace std; #define MAX_N 50000 string str; int sum; //记录总兵数 int num[MAX_N+1]={0}; //记录各个兵营的兵数 typedef struct node { int left; int right; int data; node* lchild; node* rchild; node() { left = right = data = 0; } }Tree; Tree* CreateTree(int a,int b) { Tree* r; r = (Tree*)malloc(sizeof(Tree)); r->left = a; r->right = b; if(a == b) { r->data = num[a]; r->lchild = r->rchild = NULL; } else { int mid = (a+b)>>1; r->lchild = CreateTree(a,mid); r->rchild = CreateTree(mid+1,b); r->data = r->lchild->data + r->rchild->data; } return r; } void insert(Tree* r,int a,int b) { if(r->left == a && r->right == a) { r->data += b; return; } int mid = (r->left + r->right)>>1; if(a <= mid) { insert(r->lchild,a,b); } else { insert(r->rchild,a,b); } r->data += b; } void find(Tree* r,int a,int b) { if(r->left == a && r->right == b) { sum += r->data; return; } int mid = (r->left + r->right)>>1; if(b<=mid) { find(r->lchild,a,b); } else if(a>mid) { find(r->rchild,a,b); } else { find(r->lchild,a,mid); find(r->rchild,mid+1,b); } } int main() { int t,n,x,y; int i; int ca = 0; scanf("%d",&t); while(t--) { printf("Case %d:/n",++ca); scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&num[i]); } Tree* T; T = CreateTree(1,n); while(cin>>str) { if(str == "Query") { sum = 0; scanf("%d%d",&x,&y); find(T,x,y); printf("%d/n",sum); } else if(str == "Add") { scanf("%d%d",&x,&y); insert(T,x,y); } else if(str == "Sub") { scanf("%d%d",&x,&y); insert(T,x,-y); } else { break; } } } return 0; }
.
2.hdu1754 I Hate It
更新节点,区间最值。一开始用链表建树,发现MLE了,就改成了数组建树,过!
#include using namespace std; #define MAX_N 200000 int big; typedef struct node { int left; int right; int data; int maxx; }node; node stu[MAX_N*4]; //int num[MAX_N+1]; int Max(int a,int b) { return a>b?a:b; } void CreateTree(int ii,int a,int b) { stu[ii].left = a; stu[ii].right = b; stu[ii].maxx = 0; stu[ii].data = 0; if(a == b) { return; } else { int mid = (a+b)>>1; CreateTree(ii*2,a,mid); CreateTree(ii*2+1,mid+1,b); } } void updata(int ii,int a,int b) { if(stu[ii].left == a && stu[ii].right == a) { stu[ii].data = b; stu[ii].maxx = b; } else { int mid = (stu[ii].left+stu[ii].right)>>1; if(a <= mid) { updata(ii*2,a,b); } else { updata(ii*2+1,a,b); } if(b > stu[ii].maxx) stu[ii].maxx = b; } } void find(int ii,int a,int b) { if(stu[ii].left == a && stu[ii].right == b) { //printf("%d/n",stu[ii].maxx); if(stu[ii].maxx > big) big = stu[ii].maxx; } else { int mid = (stu[ii].left + stu[ii].right)>>1; if(b <= mid) { find(ii*2,a,b); } else if(a > mid) { find(ii*2+1,a,b); } else { find(ii*2,a,mid); find(ii*2+1,mid+1,b); } } } int main() { int n,m,num; int i; while(scanf("%d%d",&n,&m)!=EOF) { CreateTree(1,1,n); for(i=1;i<=n;i++) { scanf("%d",&num); updata(1,i,num); } char c; int x1,x2; while(m--) { scanf("%*c%c",&c); scanf("%d%d",&x1,&x2); if(c == 'Q') { big = 0x80000000; find(1,x1,x2); printf("%d/n",big); } else { updata(1,x1,x2); } } } return 0; }
.
3.hdu1698 Just a Hook
成段更新,总区间求和.也不是很难,注意递归时一层一层的递归下去。写的时候中间忽略了一点,造成错了好几次!
#include using namespace std; #define MAX_N 100000 struct node { int left; int right; int data; int sum; }; node hook[4*MAX_N]; void CreateTree(int ii,int a,int b) { hook[ii].left = a; hook[ii].right = b; if(a == b) { hook[ii].data = 1; hook[ii].sum = 1; } else { int mid = (a+b)>>1; CreateTree(ii*2,a,mid); CreateTree(ii*2+1,mid+1,b); hook[ii].data = 0; hook[ii].sum = (hook[ii*2].sum + hook[ii*2+1].sum); } } void updata(int ii,int a,int b,int c) { if(hook[ii].left == a && hook[ii].right == b) { hook[ii].data = c; hook[ii].sum = (b-a+1)*c; } else { if(hook[ii].data > 0) { hook[ii*2].data = hook[ii].data; hook[ii*2].sum = (hook[ii*2].right - hook[ii*2].left+1)*hook[ii*2].data; hook[ii*2+1].data = hook[ii].data; hook[ii*2+1].sum = (hook[ii*2+1].right - hook[ii*2+1].left+1)*hook[ii*2+1].data; } hook[ii].data = 0; //写的时候这个丢掉了。一直错 int mid = (hook[ii].left + hook[ii].right)>>1; if(b <= mid) { updata(ii*2,a,b,c); } else if(a > mid) { updata(ii*2+1,a,b,c); } else { updata(ii*2,a,mid,c); updata(ii*2+1,mid+1,b,c); } hook[ii].sum = hook[ii*2].sum + hook[ii*2+1].sum; } } int main() { int t,n,m,x1,x2,x3; int i; scanf("%d",&t); for(i=1;i<=t;i++) { scanf("%d",&n); CreateTree(1,1,n); scanf("%d",&m); while(m--) { scanf("%d%d%d",&x1,&x2,&x3); updata(1,x1,x2,x3); } printf("Case %d: The total value of the hook is %d./n",i,hook[1].sum); } return 0; }
.
4.hdu1394 Minimum Inversion Number
更新节点,区间求和(实现nlog(n)的逆序数方法,和树状数组比起来实在是有点鸡肋)
.
5.hdu1779 9-Problem C暂不公开
成段更新,区间最值
.
6.pku2777 Count Color
成段更新,区间统计,位运算加速(我总把query里的传递给子节点的步骤忘了)。错了蛮多次的,一开始是RE,应该是中间跑的时候循环有问题,后来的话WA,find函数中存在问题!
#include #include using namespace std; #define MAX_N 100000 int t; int num[35]; typedef struct node { int left; int right; int data; node* lchild; node* rchild; }Tree; Tree* CreateTree(int a,int b) { Tree* r; r=(Tree*)malloc(sizeof(Tree)); r->left = a; r->right = b; r->data = 1; if(a+1==b) r->lchild=r->rchild=NULL; else { int mid = (a+b)>>1; r->lchild = CreateTree(a,mid); r->rchild = CreateTree(mid,b); } return r; } void search(Tree* r,int a,int b,int c) { //if(r->data == c) // return; if(r==NULL) return ; if(r->left == a && r->right == b) { r->data = c; } else { if(r->data > 0) { r->lchild->data=r->data; r->rchild->data=r->data; } r->data = 0; int mid = (r->left + r->right)>>1; if(a >= mid) { search(r->rchild,a,b,c); } else if(b <= mid) { search(r->lchild,a,b,c); } else { search(r->lchild,a,mid,c); search(r->rchild,mid,b,c); } //if(r->lchild->data == r->rchild->data) // { // r->data = r->lchild->data; // } } } void find(Tree* r,int a,int b) { /*if(r->left == a && r->right == b) { if(r->data > 0) { //num[r->data][0]++; if(num[r->data][0] == 0) { num[r->data][0]++; num[r->data][1] = r->right; } else { if(num[r->data][1] == r->left) num[r->data][1] = r->right; else { num[r->data][0]++; num[r->data][1] = r->right; } } } else if(r->data == 0) { int mid = (a+b)>>1; find(r->lchild,a,mid); find(r->rchild,mid,b); } return; }*/ if(r==NULL) return ; if(r->data > 0) { num[r->data]++; return; } int mid = (r->left + r->right)>>1; if(a >= mid) { find(r->rchild,a,b); } else if(b <= mid) { find(r->lchild,a,b); } else { find(r->lchild,a,mid); find(r->rchild,mid,b); } } int Sum() { int i; int sum = 0; for(i=1;i<=t;i++) { if(num[i] != 0) sum++; } return sum; } int main() { int l,m; while(scanf("%d%d%d",&l,&t,&m)!=EOF) { Tree* T; T = CreateTree(1,l+1); while(m--) { int x1,x2,color; char c; cin>>c; if(c == 'C') { scanf("%d%d%d",&x1,&x2,&color); if(x1>x2) swap(x1,x2); search(T,x1,x2+1,color); } else { memset(num,0,sizeof(num)); scanf("%d%d",&x1,&x2); if(x1>x2) swap(x1,x2); find(T,x1,x2+1); printf("%d/n",Sum()); } } } return 0; }
.
7.pku3468 A Simple Problem with Integers
成段更新,区间求和(中间乘法会超int..纠结了)
这题int因为一开始就注意,没出什么问题。但错了很多次,在成段更新的时候不能更新到点,这样肯定对超时的。这题是要加一个增量,表示该节点增加了多少。现在也不是很明白。线段树觉的重要的就是node中那些个域!
#include using namespace std; #define MAX_N 100000 __int64 sum = 0; __int64 num[MAX_N+1]; struct node { int left; int right; __int64 count; //表示该节点的总个数 __int64 data; //表示增量的那个数 }; node N[4*MAX_N]; void CreateTree(int ini,int a,int b) { N[ini].left = a; N[ini].right = b; N[ini].data = 0; if(a == b) { N[ini].count= num[a]; } else { int mid = (a+b) >> 1; if(b <= mid) CreateTree(ini*2,a,b); else if(a > mid) CreateTree(ini*2+1,a,b); else { CreateTree(ini*2,a,mid); CreateTree(ini*2+1,mid+1,b); } N[ini].count= N[ini*2].count+ N[ini*2+1].count; } } void insert(int ini,int a,int b,int c) { if(N[ini].left == a && N[ini].right == b) { N[ini].data += c; //N[ini].count+= (b-a+1) * c; } else { int mid = (N[ini].left + N[ini].right) >> 1; if(b <= mid) insert(ini*2,a,b,c); else if(a > mid) insert(ini*2+1,a,b,c); else { insert(ini*2,a,mid,c); insert(ini*2+1,mid+1,b,c); } N[ini].count = (N[ini*2].count + (N[ini*2].right - N[ini*2].left + 1) * N[ini*2].data) + (N[ini*2+1].count + (N[ini*2+1].right - N[ini*2+1].left + 1) * N[ini*2+1].data); } } void find(int ini,int a,int b) { if(N[ini].left == a && N[ini].right == b) { sum += N[ini].count + N[ini].data*(b-a+1); } else { N[ini*2].data += N[ini].data; N[ini*2+1].data += N[ini].data; N[ini].count += N[ini].data * (N[ini].right - N[ini].left + 1); N[ini].data = 0; int mid = (N[ini].left + N[ini].right) >> 1; if(b <= mid) find(ini*2,a,b); else if(a > mid) find(ini*2+1,a,b); else { find(ini*2,a,mid); find(ini*2+1,mid+1,b); } } } void Init() { int n,m,x1,x2,x3; char c; int i; while(scanf("%d%d",&n,&m)!=EOF) { for(i=1;i<=n;i++) scanf("%I64d",&num[i]); CreateTree(1,1,n); while(m--) { scanf("%*c%c",&c); if(c == 'Q') { sum = 0; scanf("%d%d",&x1,&x2); find(1,x1,x2); printf("%I64d/n",sum); } else { scanf("%d%d%d",&x1,&x2,&x3); insert(1,x1,x2,x3); } } } } int main() { Init(); return 0; }
.
8.pku2528 Mayor’s posters
成段更新,区间统计(离散化)
一个很恶心的题目,昨天晚上看了题意,上网了解了一下离散化。离散化就是为了缩短线段的范围,如两个线段(1,6)和(4,9),离散化一下就是1->1,6->3,4->2,9->4,那么离散后的线段就是(1,3)和(2,4),把线段长度从(1,9)缩短到了(1,4),这种离散化是很实用的。离散的过程就是先把线段的坐标保存下来(1,6,4,9),再排序(1,4,6,9),之后对应(1->1,4->2,6->3,9->4),再根据这个对应修改原先的线段就好了。
写的时候很恶心,一直re,很久以后试试看的态度改了MAX_N,从10005改成30005就行了。应该是用来排序的那个数组开小了
#include #include using namespace std; #define MAX_N 30005 //一开始开10005 就RE了 int n; //线段个数 int num[10005]; int pos[10005][2]; bool vis[10000005]; int hash[10000005]; int set[8*MAX_N]; struct node { int left; int right; int data; }; node T[8*MAX_N]; void CreateTree(int ini,int a, int b) { T[ini].left = a; T[ini].right = b; T[ini].data = -1; if(a >= b) return; int mid = (a+b)>>1; CreateTree(ini*2,a,mid); CreateTree(ini*2+1,mid+1,b); } void insert(int ini,int a,int b,int color) { if(T[ini].left == a && T[ini].right == b) { T[ini].data = color; //return; } else { if(T[ini].data > 0 && T[ini].data != color) { T[ini*2].data = T[ini].data; T[ini*2+1].data = T[ini].data; T[ini].data = 0; } else if(T[ini].data == -1) { T[ini].data = 0; } int mid = (T[ini].left+T[ini].right)>>1; if(b <= mid) insert(ini*2,a,b,color); else if(a > mid) insert(ini*2+1,a,b,color); else { insert(ini*2,a,mid,color); insert(ini*2+1,mid+1,b,color); } } } void find(int ini) { if(T[ini].left == T[ini].right) { if(T[ini].data > 0) num[T[ini].data]++; return; } if(T[ini].data == -1) return; else if(T[ini].data > 0) { num[T[ini].data]++; return; } else if(T[ini].data == 0) { find(ini*2); find(ini*2+1); } } int cmp(int a,int b) { return a 0) sum++; } printf("%d/n",sum); } return 0; }
.
9.hdu2795 Billboard
更新节点,询问特殊(暑假的时候不会线段树被这水题狂虐)
一开始不能理解,不会建树,一直以为是用10^9,实际上只要20000*4就行了。而data表示最大值。想通这个就好过了。
#include using namespace std; #define MAX_N 200000 int h,w,n; struct node { int left; int right; int data; //表示这一段中最大的数 }; node T[4*MAX_N]; int Max(int a,int b) { return a>b?a:b; } void CreateTree(int ini,int a,int b) { T[ini].left = a; T[ini].right = b; T[ini].data = w; //一开始最大都是宽度 if(a == b) { return; } else { int mid = (a + b) >> 1; CreateTree(ini*2,a,mid); CreateTree(ini*2+1,mid+1,b); } } void insert(int ini,int a) { if(T[ini].left == T[ini].right) { printf("%d/n",T[ini].left); T[ini].data -= a; } else { if(T[ini*2].data >= a) { insert(ini*2,a); } else { insert(ini*2+1,a); } T[ini].data = Max(T[ini*2].data,T[ini*2+1].data); } } int main() { int i,num; while(scanf("%d%d%d",&h,&w,&n)!=EOF) { if(h < n) CreateTree(1,1,h); else CreateTree(1,1,n); //建树按照min(h,n)来建,这样就保证最大时n。 for(i=0;i T[1].data) { printf("-1/n"); } else insert(1,num); } } return 0; }
.
10.pku3667 Hotel
成段更新,寻找空间(经典类型,求一块满足条件的最左边的空间)
.
11.hdu1540 Tunnel Warfare
更新节点,询问节点所在区间(同上一道Hotel一样类型的题目)
.
12.hdu2871 Memory Control
hotel变形题目,三个都函数一样(vector忘记清空检查了好久)
.
13.hdu3016 Man Down
成段更新,单点查询(简单线段树+简单DP)
.
14.hdu1542 Atlantis
矩形面积并,扫描线法(发现我们HDU的队员的矩形面积交模板基本都是在最坏情况下更新到底,宁波惨痛的教训啊..)
.
15.hdu1255 覆盖的面积
同上,扫描线法,我多加了一个系数csum,来统计覆盖两次的长度(156MS,第一次做线段树排到第一,纪念下)
.
16.hdu1828 Picture
扫描线,同面积统计,加了一个num_Seg统计一个扫描区域里的边
.
17.pku1436 Horizontally Visible Segments
成段更新,成段询问(染色问题,坐标*2后很简单,最后统计用暴力- -)
.
18.pku3225 Help with Intervals
成段更新,总询问区间(有个异或操作比较新颖)
.
19.pku2482 Stars in Your Window
成段更新,区间最值 + 扫描线(转化成区间最值有点巧妙,数据太TMD的恶心了,中间二分的地方会int溢出,检查了半个小时)
.
20.pku2828 Buy Tickets
思维很巧妙,倒过来做的话就能确定此人所在的位置
.
21.pku2464 Brownie Points II
更新节点,区间求和 + 扫描线(很好玩的一道题目,用两个线段树沿着扫描线更新,然后按”自己最多,对方最少”的方案一路统计)
(因为两棵树,我就直接写成类了)
.
22.pku3145 Harmony Forever
查找一个区间内最左边的数,询问的val大的话用线段树,小的话线性扫描,很囧的题目
.
23.pku2886 Who Gets the Most Candies?
寻找区间中的左数第N个数,约瑟夫环(学到了反素数表,可以不用把所有数字预处理出来了)
.
24.pku2991 Crane
记录了线段的两端点以及转过的角度,成段的转,超有意思的题目,做了之后会对线段树理解更深刻
(wy教主推荐的,不过我的代码写的太搓了..没啥学习的价值)
.
25.hdu1823 Luck and Love
二维线段树,没啥意思,代码量大了一倍而已,题目和运用范围都没一维的广,随便找了道题目练习下就好