线段树和树状数组的区别
假设数组长度为n。
线段树和树状数组的基本功能都是在某一满足结合律的操作(比如加法,乘法,最大值,最小值)下,
O(logn)的时间复杂度内修改单个元素并且维护区间信息。
不同的是,树状数组只能维护前缀“操作和”(前缀和,前缀积,前缀最大最小),而线段树可以维护区间操作和。
但是某些操作是存在逆元的,这样就给人一种树状数组可以维护区间信息的错觉:维护区间和,模质数意义下的区间乘积,区间xor和。能这样做的本质是取右端点的前缀和,然后对左端点左边的前缀和的逆元做一次操作,所以树状数组的区间询问其实是在两次前缀和询问。
所以我们能看到树状数组能维护一些操作的区间信息但维护不了另一些的:最大/最小值,模非质数意义下的乘法,原因在于这些操作不存在逆元,所以就没法用两个前缀和做。
而线段树就不一样了,线段树直接维护的就是区间信息,所以一切满足结合律的操作都能维护区间和,并且lazy标记的存在还能使线段树能够支持区间修改,这点是树状数组做不到的。
可以说树状数组能做的事情其实是线段树的一个子集,大多数情况下使用树状数组真的只是因为它好写并且常数小而已。
不过随着zkw线段树的普及,树状数组仅有的两点优势也不复存在了……估计要成为时泪了吧。
hdu 1166 板子题
单点修改+区间求和
三个月前过的 现在甚至快忘了。。。真奇喵~
/*Accepted
Time 249ms
Memory 2244kB*/
#include
#define maxn 55555
int sum[maxn<<2];
void pushup(int rt)
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt)
{
if(l==r)
{
scanf("%d",&sum[rt]);
return ;
}
int m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushup(rt);
}
//update a[p]==add
void update(int p,int add,int l,int r,int rt)
{
if(l==r)
{
sum[rt]+=add;
return ;
}
int m=(l+r)>>1;
if(p<=m)update(p,add,l,m,rt<<1);
else update(p,add,m+1,r,rt<<1|1);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
return sum[rt];
}
int m=(l+r)>>1;
int result=0;
if(L<=m)result+=query(L,R,l,m,rt<<1);
if(R>m)result+=query(L,R,m+1,r,rt<<1|1);
return result;
}
int main()
{
int t,n;
scanf("%d",&t);
for(int cas=1;cas<=t;cas++)
{
printf("Case %d:\n",cas);
scanf("%d",&n);
build(1,n,1);
char op[10];
while(scanf("%s",op))
{
if(op[0]=='E') break;
int a,b;
scanf("%d%d",&a,&b);
if(op[0]=='Q')printf("%d\n",query(a,b,1,n,1));
else if(op[0]=='S')update(a,-b,1,n,1);
else if(op[0]=='A')update(a,b,1,n,1);
}
}
return 0;
}
hdu 1754
单点修改+区间最大值维护
/*Accepted
Time
780ms
Memory
3784kB
*/
#include
#include
#include
using namespace std;
#define maxn 200000+10
int MAX[maxn<<2];
void pushup(int rt)
{
MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]);
}
void build(int l,int r,int rt)
{
if(l==r)
{
scanf("%d",&MAX[rt]);
return ;
}
int m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushup(rt);
}
//update a[p]==add
void update(int p,int add,int l,int r,int rt)
{
if(l==r)
{
MAX[rt]=add;
return ;
}
int m=(l+r)>>1;
if(p<=m)update(p,add,l,m,rt<<1);
else update(p,add,m+1,r,rt<<1|1);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
return MAX[rt];
}
int m=(l+r)>>1;
int result=0;
if(L<=m)result=max(query(L,R,l,m,rt<<1),result);
if(R>m)result=max(query(L,R,m+1,r,rt<<1|1),result);
return result;
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
build(1,n,1);
char op[10];
int a,b;
while(m--)
{
scanf("%s",op);
scanf("%d%d",&a,&b);
if(op[0]=='Q')printf("%d\n",query(a,b,1,n,1));
else if(op[0]=='U')update(a,b,1,n,1);
}
}
return 0;
}
hdu 1394
线段树维护逆序对 //给的是0~n-1
先用o(nlogn)求出来一开始的逆序对,然后o(1)求出最小的。
求逆序数的方法很多!
数据小的时候可以直接两重循环暴力,可以用归并排序,树状数组,线段树!
用线段树就是查找比当前这个数大的已存入线段树中的个数。
比如求a[i],那么就查找当前线段树(a[i]+1,n)中的个数。。然后把a[i]更新到线段树中。
这样考虑:一个数字 b[i] 在开头,比它大的数字有 b[i] 个,也就是说和这个数字组成了 b[i] 个逆序,把它放到最后,这个数字可以组成 n-1-b[i] 个逆序,所以逆序数的增量是 n - 1 - b[i] - b[i] ,这样就可以根据原来的序列的逆序数求出剩下的所有序列的逆序数了~
#include
#include
#include
#define maxn 5555
/*Accepted
Time 78ms
Memory 1756kB */
using namespace std;
int sum[maxn<<2];
int p[maxn];
void pushup(int rt)
{
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int l,int r,int rt)
{
sum[rt]=0;
if(l==r)
{
return ;
}
int m=(l+r)>>1;
build(l,m,rt<<1);
build(m+1,r,rt<<1|1);
pushup(rt);
}
//update a[p]==add
void update(int p,int l,int r,int rt)
{
if(l==r)
{
sum[rt]++;
return ;
}
int m=(l+r)>>1;
if(p<=m)update(p,l,m,rt<<1);
else update(p,m+1,r,rt<<1|1);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
return sum[rt];
}
int m=(l+r)>>1;
int result=0;
if(L<=m)result+=query(L,R,l,m,rt<<1);
if(R>m)result+=query(L,R,m+1,r,rt<<1|1);
return result;
}
int main()
{
int n;
while(~scanf("%d",&n))
{
int sum=0;
build(1,n,1);//初始化为0,num[1~n]=0
for(int i=1;i<=n;i++)
{
int t;
scanf("%d",&p[i]);
sum+=query(p[i]+1,n,1,n,1);
update(p[i]+1,1,n,1); //每输入数据update一次,数是0~n-1,但是树上的下标是1~n
}
int ans=sum;
for(int i=1;i<=n;i++)
{
sum+=n-(p[i]+1)-p[i];
ans=min(ans,sum);
}
printf("%d\n",ans);
}
return 0;
}
hdu 2795
这道题真的使我懵逼了
我有点不懂 if(r==l)这时候rt?=l? or not???l==r证明走到底部了 当然不会等于rt rt是他的编号哇 我是傻子
于是mark 一下线段树的原理图吧https://blog.csdn.net/qq_36524645/article/details/81774860
毕竟改写模板题是大换血的思维?吐血。。。
另外这题有一个很奇妙的bug 我re了超多 是爆栈了?(第一个代码)
不懂了 我研究一下。。。。我好碎碎念啊。。。。。
自从听yzd dalao 说了手动扩栈 正可以一试(第二个代码) 是我走火入魔?为啥网上大家都是显然有这个控制条件。。。
自闭了 这么一个水题
/*Accepted
Time 2698ms
Memory 3796kB*/
#include
#include
#include
using namespace std;
#define maxn 320000
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
int h,w,n;
int MAX[maxn<<2];
void pushup(int rt)
{
MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]);
}
void build(int l,int r,int rt)
{
MAX[rt]=w;//一定要在if外边搞 要不永远进不去貌似
if(l==r)
{
return;
}
int m=(l+r)>>1;
build(ls);
build(rs);
//每个值初始化都一样就不用update
}
int query(int add,int l,int r,int rt)
{
if(l==r)
{
//cout<>1;
int re=(MAX[rt<<1]>=add)?query(add,ls):query(add,rs);
pushup(rt);//有更改所以要更新
return re;
}
int main()
{
while(~scanf("%d%d%d",&h,&w,&n))
{
if(h>n)h=n;//不加这个就导致我dev崩了!!!导致oj re了????什么牛鬼蛇神
build(1,h,1);
//cout<
好像不行 是re的好像是越界了??
问题是我就不明白这一句话这么大作用?!!
//RE 手动扩栈了 但迷了稍微优化一下长度真的有用????
#include
#include
#include
using namespace std;
#define maxn 320000
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
#pragma comment(linker, "/STACK:102400000,102400000")
int h,w,n;
int MAX[maxn<<2];
void pushup(int rt)
{
MAX[rt]=max(MAX[rt<<1],MAX[rt<<1|1]);
}
void build(int l,int r,int rt)
{
MAX[rt]=w;//一定要在if外边搞 要不永远进不去貌似
if(l==r)
{
return;
}
int m=(l+r)>>1;
build(ls);
build(rs);
//每个值初始化都一样就不用update
}
int query(int add,int l,int r,int rt)
{
if(l==r)
{
//cout<>1;
int re=(MAX[rt<<1]>=add)?query(add,ls):query(add,rs);
pushup(rt);//有更改所以要更新
return re;
}
int main()
{
while(~scanf("%d%d%d",&h,&w,&n))
{
//if(h>n)h=n;//不加这个就导致我dev崩了!!!导致oj re了????什么牛鬼蛇神
build(1,h,1);
//cout<
poj 2828 (类似约瑟夫)
mark 一个博客吧 有图解
https://www.cnblogs.com/CheeseZH/archive/2012/04/29/2476134.html
总之这个题是大概用线段树维护空的地方 维护空的个数
//Accepted
//Time 3407ms
//Memory 4728kB
#include
#define maxn 220000
#define ls l,m,rt<<1
#define rs m+1,r,rt<<1|1
int pos[maxn],val[maxn],sum[maxn<<2],re[maxn];
int id;
void build(int l ,int r,int rt)
{
sum[rt]=r-l+1;
if(l==r)
{
return ;
}
int m=(l+r)>>1;
build(ls);
build(rs);
}
void update(int p,int l ,int r,int rt)
{
sum[rt]--;
if(l==r)
{
id=l;
return;
}
int m=(l+r)>>1;
if(sum[rt<<1]=p,应该是rt<<1 比较ls
{
p-=sum[rt<<1];//把左边已经排上队的人数减去 便是在右边的名次
update(p,rs);
}
else update(p,ls);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
build(1,n,1);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&pos[i],&val[i]);
}
for(int i=n;i>=1;i--)
{
update(pos[i]+1,1,n,1);
re[id]=val[i];
}
for(int i=1;i<=n;i++)
{
if(i==1) printf("%d",re[i]);
else printf(" %d",re[i]);
}
printf("\n");
}
}
poj 2886 反素数+线段树(类似约瑟夫)
mark 反素数:https://www.cnblogs.com/liuweimingcprogram/p/5877411.html
等比完赛回来啃完线段树接着啃数论吧 https://blog.csdn.net/my_sunshine26/article/details/72772637
//这道题是2828的一道类似的题
//模型几乎没啥变化
//就是mark 了一下反素数这种操作
#include
#include
using namespace std;
#define maxn 500010
#define mid ((l+r)>>1)
#define tmp (st<<1)
#define lson l,mid,tmp
#define rson mid+1,r,tmp|1
int sum[maxn<<2];
#define mod sum[1]
struct N{
char s[100];
int num;
void in(){
scanf("%s%d",s,&num);
}
}my[maxn];
void build(int l,int r,int st){
sum[st]=r-l+1;
if(l==r)return ;
build(lson);
build(rson);
}
int update(int c,int l,int r,int st){
sum[st]--;
if(l==r)return r;
if(c<=sum[tmp])return update(c,lson);
return update(c-sum[tmp],rson);
}
int antiprime[]={
1, 2, 4, 6, 12,
24, 36, 48, 60, 120,
180, 240, 360, 720, 840,
1260, 1680, 2520, 5040, 7560,
10080, 15120, 20160, 25200, 27720,
45360, 50400, 55440, 83160, 110880,
166320, 221760, 277200, 332640, 498960};
int factorNum[]={
1, 2, 3, 4, 6,
8, 9, 10, 12, 16,
18, 20, 24, 30, 32,
36, 40, 48, 60, 64,
72, 80, 84, 90, 96,
100,108,120,128,144,
160,168,180,192,200};
int main(){
int n,k;
while(scanf("%d%d",&n,&k)!=EOF){
build(1,n,1);
for(int i=1;i<=n;++i)my[i].in();
int ans=0;
while(ans<35&&antiprime[ans]<=n)ans++;
ans--;
int pos=0;
my[pos].num=0;
for(int i=0;i0)
k=((k-1+my[pos].num-1)%mod+mod)%mod+1;//√
else
k=((k-1+my[pos].num)%mod+mod)%mod+1;//不是很懂
pos=update(k,1,n,1);
}
printf("%s %d\n",my[pos].s,factorNum[ans]);
}
return 0;
}