您的位置:首页 > 其它

杭电 线段树 (个人整理(基础入门版))(多题练习.)(简单风格)

2015-12-04 13:33 411 查看
整理了很多杭电上边的题.也从其他很多博客那里学会了新的东西,一时兴起 整理出来留给大家.

首先介绍一下自己的线段树风格:

int tree[](树)


#define lson l,m,rt*2//左子树模板.

#define rson m+1,r,rt*2+1//右子树模板.


看了很多博客写的线段树的教学 感觉大多数都是用结构体来构建节点的,但是怎么看都感觉并不是那么简洁(我的强迫症~~~~)

蓝后默默的学习了一种肥猪流的线段树风格 ,如果读者喜欢的话可以模仿一下~(其实我也是偷偷从一个大牛处学习来的).

这里用一个建树操作来介绍这种肥猪流风格有什么特点

void build( int l ,int r , int rt )
{
	if( l == r )
	{
		scanf("%d",&tree[rt]);
		return ;
	}
	else
	{
		int m = (l+r)>>1 ;
		build(lson) ;
		build(rson) ;
		pushup(rt) ;
	}
}

不知道这种风格是否符合您的口味,这里贴上大多数博主的写法进行对比

void build(int root,int L,int R)
  segTree[root].lc=L;
  segTree[root].rc=R;
  if(L==R){
    segTree[root].sum=0;
    return ;
  }
  int mid = (L + R) >> 1;
  build(root<<1,L,mid);
  build(root<<1|1,mid+1,R);
  segTree[root].sum=segTree[root<<1].sum+segTree[root<<1|1].sum;
}

不知道读者你们的感受如何 但是我还是依然觉得第一种比较简洁一些。之后我所有的代码也都会用我的风格来写~

如果喜欢我的代码风格欢迎转载

详解题目之前我先贴一份线段树的图片 和自己经常使用的一份模板:

#define lson l,m,rt*2

#define rson m+1,r,rt*2+1
void pushup(int rt)

{

tree[rt]=tree[rt<<1]+tree[rt<<1|1];

}

int Query(int L,int R,int l,int r,int rt)

{

if(L<=l&&r<=R)

{

return tree[rt];

}

else

{

int m=(l+r)>>1;

int ans=0;

if(L<=m)

{

ans+=Query(L,R,lson);

}

if(m<R)

{

ans+=Query(L,R,rson);

}

return ans;

}

}

void build( int l ,int r , int rt )

{

if( l == r )

{

scanf("%d",&tree[rt]);

return ;

}

else

{

int m = (l+r)>>1 ;

build(lson) ;

build(rson) ;

pushup(rt) ;

}

}

void update(int p,int c,int l,int r,int rt)//p阵营c数据.

{

//printf("%d %d %d %d %d\n",p,c,l,r,rt);

if(l==r)

{

tree[rt]+=c;

}

else

{

int m=(l+r)>>1;

if(p<=m) update(p,c,lson);

else update(p,c,rson);

pushup(rt);

//printf("sum[%d]:%d\n",rt,tree[rt]);

}

}

详解几个比较经典一点的杭电的线段树的题目:

杭电1166(敌兵布阵)

敌兵布阵

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

Total Submission(s): 63909 Accepted Submission(s): 26950



Problem Description
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以内。



Sample Input
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End




Sample Output
Case 1:
6
33
59




/*单点更改数据 区间询问值.

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
//左乘2右除2.|加一0.0;
#define lson l,m,rt<<1//(rt*2);
#define rson m+1,r,(rt<<1)|1//(rt*2+1);

int tree[50005<<2];

void pushup(int rt)//维护线段树数据(从下往上.
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
int Query(int L,int R,int l,int r,int rt)//(L,R)区间获取sum
{
if(L<=l&&r<=R)//如果满足一个区间
{
return tree[rt];//就返回一个区间的值
}
else
{
int m=(l+r)>>1;
int ans=0;
if(L<=m)//如果目的区间的最左边的阵营在罚分点左边
{
ans+=Query(L,R,lson);//就要继续在左子树寻找目的区间(也可能是目的点)
}
if(m<R)
{
ans+=Query(L,R,rson);//同理
}
return ans;
}
}
void build( int l ,int r , int rt )
{
if( l == r )
{
scanf("%d",&tree[rt]);//对叶子输入数据.
return ;
}
else
{
int m = (l+r)>>1 ;
build(lson) ;
build(rson) ;
pushup(rt) ;//从下向上维护数据.
}
}
void update(int p,int c,int l,int r,int rt)//p阵营c数据.
{
//printf("%d %d %d %d %d\n",p,c,l,r,rt);
if(l==r)//找到了p阵营
{
tree[rt]+=c;//加上c数据
}
else
{
int m=(l+r)>>1;//m是二叉的罚分点.
if(p<=m) update(p,c,lson);//如果p阵营明显在左子树.向左边找
else update(p,c,rson);//右边找
pushup(rt);//当然还是要维护数据.
//printf("sum[%d]:%d\n",rt,tree[rt]);
}
}
int main()
{
int t;
int kase=0;
scanf("%d",&t);
while(t--)
{
printf("Case %d:\n",++kase);
memset(tree,0,sizeof(tree));
int n;
scanf("%d",&n);
build(1,n,1);
char s[30];int a,b;
/*for(int i=0;i<30;i++)
printf("%d ",tree[i]);*/
while(~scanf("%s",s))//对应操作.
{
if(s[0]=='E')break;
scanf("%d%d",&a,&b);
if(s[0]=='Q')
printf("%d\n",Query(a,b,1,n,1));
else if(s[0]=='S')
update(a,-b,1,n,1);
else
update(a,b,1,n,1);
}
}
}

杭电1754 (I hate it)

I Hate It

Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)

Total Submission(s): 54986 Accepted Submission(s): 21496

Problem Description
很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。

这让很多学生很反感。

不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。


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
对于每一次询问操作,在一行里面输出最高成绩。


Sample Input
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5


Sample Output
5
6
5
9


Hint

Huge input,the C function scanf() will work better than cin

/*单点更改数据 区间询问值(求最大值)*/
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define lson l,m,rt*2
#define rson m+1,r,rt*2+1
#define inf 0x1f1f1f1f
int tree[200005*8];
int maxn;
void pushup(int rt)
{
tree[rt]=max(tree[rt*2],tree[rt*2+1]);//对比1166题 加和变成了取最大值
}
void build(int l,int r,int rt)//建树.
{
if(l==r)
{
scanf("%d",&tree[rt]);
return ;
}
else
{
int m=(l+r)/2;
build(lson);
build(rson);
pushup(rt);
}
}
int Q(int L,int R,int l,int r,int rt)//对比1166题 加和变成了max比较
{
if(L<=l&&r<=R)
{
return tree[rt];
}
else
{
int m=(l+r)/2;
if(L<=m)
{
maxn=max(Q(L,R,lson),maxn);
}
if(m<R)
{
maxn=max(Q(L,R,rson),maxn);
}
return maxn;
}
}
void update(int p,int c,int l,int r,int rt)//单点更改数据
{
if(l==r)
{
tree[rt]=c;//更改数据
}
else
{
int m=(l+r)/2;
if(p<=m)update(p,c,lson);
else update(p,c,rson);
pushup(rt);
}
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
memset(tree,0,sizeof(tree));
build(1,n,1);
char c[2];
while(m--)
{
maxn=-inf;
int a,b;
scanf("%s%d%d",c,&a,&b);
if(c[0]=='U')
update(a,b,1,n,1);
if(c[0]=='Q')
printf("%d\n",Q(a,b,1,n,1));
}
}
}
杭电 1556(color the ball)

Color the ball

Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)

Total Submission(s): 13785 Accepted Submission(s): 6915

Problem Description
N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?


Input
每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。

当N = 0,输入结束。


Output
每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数。


Sample Input
3
1 1
2 2
3 3
3
1 1
1 2
1 3
0


Sample Output
1 1 1
3 2 1


Author
8600


Source
HDU 2006-12 Programming Contest

/*对一段区间改变值 区间询问值*/
#include<stdio.h>
#include<string.h>
using namespace std;
int output[100000<<2];//这里表示对叶子的涂色次数进行统计的数组.
int tree[100000<<2];
int cont;
#define lson l,m,rt*2
#define rson m+1,r,rt*2+1
void pushdown(int rt)//向下维护数组(用处很大 在下边函数里边进行解释)
{
tree[rt*2]+=tree[rt];
tree[rt*2+1]+=tree[rt];
}
void build( int l ,int r , int rt )//建树.
{
if( l == r )
{
tree[rt]=0;
return ;
}
else
{
int m = (l+r)>>1 ;
build(lson) ;
build(rson) ;
}
}
void query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)//区间加1.//在sum操作里边会有pushdown的操作来维护线段树的数据.(这个步骤非常重要 也非常的巧妙)
{
tree[rt]++;//直接涂一个区间
return ;
}
else
{
int m=(l+r)/2;
if(L<=m)query(L,R,lson);
if(m<R) query(L,R,rson);
}
}
void sum(int l,int r,int rt)
{
if(l==r)
{
output[cont]=tree[rt];//保存数据并且直接输出~.
cont++;
return ;
}
int m=(l+r)/2;
pushdown(rt);//因为在区间上加的1,同时就要在子树上也+1....(多想想 很容易理解)
sum(lson);
sum(rson);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
memset(tree,0,sizeof(tree));
if(n==0)break;
build(1,n,1);
int x,y;
for(int i=0;i<n;i++)
{
scanf("%d%d",&x,&y);
query(x,y,1,n,1);
}
cont=0;
sum(1,n,1);

for(int i=0;i<cont;i++)
{
if(i==cont-1)
printf("%d\n",output[i]);
else
printf("%d ",output[i]);
}
}
}
杭电 4893(Wow !Such Sequence!)

Wow! Such Sequence!

Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)

Total Submission(s): 3938 Accepted Submission(s): 1104

Problem Description
Recently, Doge got a funny birthday present from his new friend, Protein Tiger from St. Beeze College. No, not cactuses. It's a mysterious blackbox.

After some research, Doge found that the box is maintaining a sequence an of n numbers internally, initially all numbers are zero, and there are THREE "operations":

1.Add d to the k-th number of the sequence.

2.Query the sum of ai where l ≤ i ≤ r.

3.Change ai to the nearest Fibonacci number, where l ≤ i ≤ r.

4.Play sound "Chee-rio!", a bit useless.

Let F0 = 1,F1 = 1,Fibonacci number Fn is defined as Fn = Fn - 1 + Fn - 2 for n ≥ 2.

Nearest Fibonacci number of number x means the smallest Fn where |Fn - x| is also smallest.

Doge doesn't believe the machine could respond each request in less than 10ms. Help Doge figure out the reason.


Input
Input contains several test cases, please process till EOF.

For each test case, there will be one line containing two integers n, m.

Next m lines, each line indicates a query:

1 k d - "add"

2 l r - "query sum"

3 l r - "change to nearest Fibonacci"

1 ≤ n ≤ 100000, 1 ≤ m ≤ 100000, |d| < 231, all queries will be valid.


Output
For each Type 2 ("query sum") operation, output one line containing an integer represent the answer of this query.


Sample Input
1 1
2 1 1
5 4
1 1 7
1 3 17
3 2 4
2 1 5


Sample Output
0
22

题意:三种操作:1、将第k个数字加上d;2、求区间[a,b]上的sum;3、将区间[a,b]上的数字改成与斐波那契数最近的数字;

/*第一个第二个操作在前边两个题中有过练习 这里着重讲解第三个操作。*/

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define lson l,m,rt*2
#define rson m+1,r,rt*2+1
using namespace std;
long long int f[90];
long long int t1[4*100005];//相当于tree
long long int t2[4*100005];//伴随树 其中的数据都是tree树数据的最近的斐波那契数.
int flag[4*100005];
long long int ans;
void pushup(int rt)//这次维护数据就要两个树一起维护了.
{
t1[rt]=t1[rt*2]+t1[rt*2+1];
t2[rt]=t2[rt*2]+t2[rt*2+1];
}
void pushdown(int rt)//因为有了覆盖操作(覆盖区间(覆盖爹))所以才要有覆盖儿子的操作
{
if(flag[rt]==1)//覆盖一段 要把儿子们覆盖上~//如果有覆盖操作的前提下
{
flag[2*rt]=flag[2*rt+1]=1;
t1[2*rt]=t2[2*rt];//直接覆盖 简单粗暴
t1[2*rt+1]=t2[2*rt+1];
flag[rt]=0;//这步骤自己想想 很容易理解
}
}
long long int getf(long long int k)//得到与k最近的斐波拉契数
{
int x = lower_bound(f,f+100,k) - f;
if(x == 0) return 1;
return f[x]-k >= k-f[x-1] ? f[x-1] : f[x];
}
void segment_update(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
t1[rt]=t2[rt];//如果是要更改数据的话 先把最高处的爹节点覆盖上区间值
flag[rt]=1;
return ;
}
pushdown(rt);//然后在用pushdown的操作覆盖儿子们.
int m=(l+r)/2;
if(L<=m)//如果(L,R)的L在左子树要有操作
{
segment_update(L,R,lson);//就继续在左子树寻找要操作的区间(也可能是点).
}
if(m<R)
{
segment_update(L,R,rson);
}
pushup(rt);
}
void update(int p,int c,int l,int r,int rt)//这部分操作也很重要 事关伴随树的数据的改变.
{
if(l==r)
{
t1[rt]+=c;//主树正常+c
t2[rt]=getf(t1[rt]);//伴随树要在主树+c之后获取斐波那契数.(伴随树的作用直接就明白了吧~)
flag[rt]=0;//同时标记为没有覆盖过数据.
return ;
}
pushdown(rt);
int m=(l+r)/2;
if(p<=m)update(p,c,lson);
else update(p,c,rson);
pushup(rt);
////////////////
}
void query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
if(flag[rt])
ans+=t2[rt];
else
ans+=t1[rt];
return ;
}
pushdown(rt);
int m=(l+r)/2;
if(L<=m)
{
query(L,R,lson);
}
if(m<R)
{
query(L,R,rson);
}
}
void build(int l,int r,int rt)
{
if(l==r)
{
t1[rt]=0;
t2[rt]=1;//菲薄纳妾数列第一个数.
flag[rt]=0;
return ;
}
else
{
int m=(l+r)/2;
build(lson);
build(rson);
pushup(rt);
}
}
void feiboinit()
{
f[0] = f[1] = 1;
int i;
for(i = 2;i < 100; i++)
f[i] = f[i-1] + f[i-2];
}
int main()
{
feiboinit();
int n,m;
while(~scanf("%d%d",&n,&m))
{
memset(t1,0,sizeof(t1));
memset(t2,0,sizeof(t2));
memset(flag,0,sizeof(flag));
build(1,n,1);//建树.
while(m--)
{
ans=0;
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(a==1)update(b,c,1,n,1);
if(a==2)
{
query(b,c,1,n,1);
printf("%I64d\n",ans);
}
if(a==3)//重点讲解操作.
{
segment_update(b,c,1,n,1);
}
}
}
}


杭电 1394(minimum inversion number)

Minimum Inversion Number

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)

Total Submission(s): 15512 Accepted Submission(s): 9463

Problem Description
The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.

For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:

a1, a2, ..., an-1, an (where m = 0 - the initial seqence)

a2, a3, ..., an, a1 (where m = 1)

a3, a4, ..., an, a1, a2 (where m = 2)

...

an, a1, a2, ..., an-1 (where m = n-1)

You are asked to write a program to find the minimum inversion number out of the above sequences.


Input
The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.


Output
For each case, output the minimum inversion number on a single line.


Sample Input
10
1 3 6 9 0 8 5 7 4 2


Sample Output
16
/*HDU 1394
逆序数 对线段树的树图理解
操作1:query
操作2: update
思路:如果这个数出现了 就使叶子上边的数据变成1还要update.根据树
图可理解如何解问题.树的作用在于求原序列的逆序数.之后也要记住逆序数
改变位置的结论.

*/
//这题交给大家自行理解(如果这个题理解深了的话会突然觉得数据结构真的很神奇.
自行百度逆序数特性~.
#include<stdio.h>
#include<string.h>
using namespace std;
#define lson l,m,rt*2
#define rson m+1,r,rt*2+1
int tree[1212121];//这个题里边的值如果是1 表示有数小于他 .
void pushup(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void build(int l,int r,int rt)
{
if(l==r)
{
tree[rt]=0;//初始化为0.
return ;
}
else
{
int m = (l+r)>>1 ;
build(lson) ;
build(rson) ;
pushup(rt) ;
}
}
int query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
return tree[rt];
}
else
{
int m=(l+r)>>1;
int ans=0;
if(L<=m)
{
ans+=query(L,R,lson);
}
if(m<R)
{
ans+=query(L,R,rson);
}
return ans;
}
}
void update(int p,int l,int r,int rt)
{
//printf("%d %d %d %d %d\n",p,l,r,rt);
if(l==r)
{
tree[rt]=1;
return ;
}
else
{
int m=(l+r)>>1;
if(p<=m) update(p,lson);
else update(p,rson);
pushup(rt);
//printf("sum[%d]:%d\n",rt,tree[rt]);
}
}
int main()
{
int n;
while(~scanf("%d",&n))
{
build(1,n,1);
int s[100000];
int sum=0;
for(int i=0;i<n;i++)
{
scanf("%d",&s[i]);
sum+=query(s[i]+1,n,1,n,1);//+1的作用也是很巧妙(躲过了不该有的操作.)//并且表示从s[i]+1到n找寻1值.(找比自己大的数)
update(s[i]+1,1,n,1);
}
int Min=sum;
for(int i=0;i<n;i++)
{
sum=sum-2*s[i]+n-1;//解题关键.
if(sum<Min)Min=sum;
}
printf("%d\n",Min);
}
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: