(我又来写题解了~~~)
树状数组(二叉搜索树)
定义:
c[i]
维护的是
[i,i−lowbit(i)+1]
这段区间的值,这个
lowbit
等会解释。树状数组,他的时间复杂度为
O(logN)
,而且常数小,还好写,简直就是oi中的大法。并且我们可以“感受”到,如果只有一般的修改与一般的询问最大值,前缀和等,都完全可以替代线段树。(当然,有些时候他也替代不了线段树,比如主席树时)。下面来直观看看这个数组c的管辖范围。
由此可见,这个数组像一棵树一样,故曰树状数组(实际上本来就是一棵树)。
下面解释
lowbit(x)
的含义:
lowbit(x)
是最大的
2k
,使得
2k
是
x
的约数,而
1.基本操作:
单点更新:
int update(int x,int val)
{
while (x<=n)
{
c[x]+=val;
x+=lowbit(x);
}
}
区间查询:
int query(int x)
{
int ans = 0;
while (x)
{
ans += c[x];
x-=lowbit(x);
}
}
实际上也可以区间更新,区间查询。记录一个新数组
例题
- Codevs 1082
题目:区间修改,区间查询。
代码:
#include<cstdio>
#include<algorithm>
const int size = 200005;
using namespace std;
typedef long long ll;
ll del[size],del2[size],num[size];
ll n , m;
ll dodo ,l ,r,val;
ll lowbit(ll x){return (x &(-x));}
void update(ll x,ll val,ll *arr)
{
while (x<=n)
{
arr[x] += val;
x += lowbit(x);
}
}
ll query(ll x,ll *arr)
{
ll ans = 0;
while(x)
{
ans += arr[x];
x -= lowbit(x);
}
return ans;
}
int main()
{
scanf("%lld" , &n);
for (ll i = 1;i<=n;i++)
{scanf("%lld" , &num[i]);
update(i,num[i] - num[i-1],del);
update(i,(i-1) * (num[i] - num[i-1]) ,del2);
}
scanf("%lld",&m);
for (ll i =1;i <=m ;i++)
{
scanf("%lld",&dodo);
if (dodo == 1)
{
scanf("%lld%lld%lld",&l,&r,&val);
update(l,val,del);
update(r+1,-val,del);
update(l,val * (l-1),del2);
update(r+1,-val * r,del2);
}
else
{
scanf("%lld%lld",&l,&r);
ll lle = (l-1) * query(l-1,del) - query(l-1,del2);
ll rr = r * query(r,del) - query(r,del2);
printf("%lld\n",rr-lle);
}
}
return 0;
}
题目: 略
题解:水题啊,注意到
c
的值很小,
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
int n,m,q;//n 对应 x //m 对应 y
int c[101][310][310];
int mat[310][310];
int lowbit(int x)
{return (x&(-x));}
using namespace std;
void update(int x,int y,int pre,int now)
{
int x0 = x;int y0 = y;
while (x0<=n)
{
y0 = y;
while (y0 <= m)
{
c[now][x0][y0]++;
y0 += lowbit(y0);
}
x0 += lowbit (x0);
}
x0 = x; y0 = y;
while (x0<=n)
{
y0 = y;
while (y0 <= m)
{
c[pre][x0][y0]--;
y0 += lowbit(y0);
}
x0 += lowbit (x0);
}
}
int query(int val,int x,int y)
{
int ans = 0;
int x0 = x;int y0 = y;
while (x0)
{
y0 = y;
while (y0)
{
ans += c[val][x0][y0];
y0 -= lowbit(y0);
}
x0 -= lowbit (x0);
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
memset(c,0,sizeof c);
for (int x = 1;x <= n ;x++)
for (int y = 1;y <= m;y++)
{
scanf("%d",&mat[x][y]);
update(x,y,0,mat[x][y]);
}
scanf("%d",&q);
while (q--)
{
int dodo;
scanf("%d",&dodo);
if (dodo == 1)
{
int x,y,val;
scanf("%d%d%d",&x,&y,&val);
update(x,y,mat[x][y],val);
mat[x][y] = val;
}else
if (dodo == 2)
{
int x1,x2,y1,y2,val;
scanf("%d%d%d%d%d",&x1,&x2,&y1,&y2,&val);
int ans = query(val,x2,y2)+ query(val,x1-1,y1-1) - query(val,x2,y1-1)- query(val,x1-1, y2);
printf("%d\n",ans);
}
}
return 0;
}
- bzoj 2743[HEOI2012]采花
题目:
萧芸斓是Z国的公主,平时的一大爱好是采花。
今天天气晴朗,阳光明媚,公主清晨便去了皇宫中新建的花园采花。花园足够大,容纳了n朵花,花有c种颜色(用整数1-c表示),且花是排成一排的,以便于公主采花。公主每次采花后会统计采到的花的颜色数,颜色数越多她会越高兴!同时,她有一癖好,她不允许最后自己采到的花中,某一颜色的花只有一朵。为此,公主每采一朵花,要么此前已采到此颜色的花,要么有相当正确的直觉告诉她,她必能再次采到此颜色的花。由于时间关系,公主只能走过花园连续的一段进行采花,便让女仆福涵洁安排行程。福涵洁综合各种因素拟定了m个行程,然后一一向你询问公主能采到多少朵花(她知道你是编程高手,定能快速给出答案!),最后会选择令公主最高兴的行程(为了拿到更多奖金!)。
题解:
实际上就是问你区间上有几个落单的数。那么我们可以打一下标记,先把询问按左端点排序,然后记录每个数的下一个与其值相等的数的位置 next ,然后先把每个值第二个加入树状数组,接着做时,就遇到一个 i ,去掉next[i] ,加上 next[next[i]] 。(因为这样就可以使如果只有一个,一正一负刚好可以抵了,反之,也对,把第二个也加上是考虑了第零个)。
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1000110;
int lowbit(int x){ return (x & (-x));}
int n , m ,ww;
int c[maxn] , a[maxn] , ans[maxn] , next[maxn] , pre[maxn ] ;
bool vis[maxn];
struct q{
int l ,r ,id;
bool operator < (const q &cx) const{
if (l == cx.l) return r < cx.r;
return l < cx.l;
}
}qq[maxn];
void update (int x,int del)
{
while (x <= n)
{
c[x] += del;
x += lowbit(x);
}
}
int query(int x)
{
if (x==0) return 0;
int ans = 0;
while (x)
{
ans += c[x];
x -= lowbit (x);
}
return ans;
}
int main()
{
scanf("%d%d%d",&n,&ww,&m);
for (int i = 1 ;i <= n ;i++)
scanf("%d" , &a[i]);
for (int i = 1;i <= m;i++)
scanf("%d%d", &qq[i].l, &qq[i].r), qq[i].id = i;
sort(qq + 1 , qq + 1 + m);
memset(vis,0,sizeof vis);
for (int i = 1;i <= n;i++)
{
if (pre[ a[i] ]) next[ pre[ a[i] ] ] = i;
else vis[i] = 1;
pre[ a[i] ] = i;
}
for (int i = 1;i <= n;i++)
if (vis[i] && next[i])
update(next[i],1);
int nowr = 1;
for(int i = 1;i <= m;i++)
{
while (nowr < qq[i].l)
{
if (next[nowr])
{
update(next[nowr] , -1);
if (next [ next [ nowr ] ]) update(next[ next[nowr] ] , 1); }
nowr++;
}
ans[qq[i].id] = query(qq[i].r) ;
}
for (int i = 1;i <= m;i++)
printf("%d\n",ans[i]);
return 0;
}
- bzoj 1878: [SDOI2009]HH的项链
题解:
与上题相似,但这题按右端点排序。我们记录一个 a[i] 表示 i 是不是当前区间内x[i] 这个值最靠右的( x 是原序列),然后就统计一下就完了。
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 200110;
int lowbit(int x){ return (x & (-x));}
int n , m;
int c[maxn] , a[maxn] , ans[maxn] , next[maxn] , pre[maxn * 5] ;
struct q{
int l ,r ,id;
bool operator < (const q &cx) const{
if (r == cx.r) return l < cx.l;
return r < cx.r;
}
}qq[maxn];
void update (int x,int del)
{
while (x <= n)
{
c[x] += del;
x += lowbit(x);
}
}
int query(int x)
{
if (x==0) return 0;
int ans = 0;
while (x)
{
ans += c[x];
x -= lowbit (x);
}
return ans;
}
int main()
{
scanf("%d",&n);
for (int i = 1 ;i <= n ;i++)
scanf("%d" , &a[i]);
scanf("%d" , &m);
for (int i = 1;i <= m;i++)
scanf("%d%d", &qq[i].l, &qq[i].r), qq[i].id = i;
sort(qq + 1 , qq + 1 + m);
for (int i = 1;i <= n;i++)
{
if (pre[ a[i] ]) next[ pre[ a[i] ] ] = i;
pre[ a[i] ] = i;
}
memset(pre , 0 , sizeof pre);
for (int i = 1;i <= n;i++) if (next[i]) pre[ next[i] ] = i;
int nowr = 0;
for(int i = 1;i <= m;i++)
{
while (nowr < qq[i].r)
{
nowr++;
if (pre[nowr]) update(pre[nowr] , -1);
if (nowr) update(nowr , 1);
}
ans[qq[i].id] = query(qq[i].r) - query(qq[i].l-1);
}
for (int i = 1;i <= m;i++)
printf("%d\n",ans[i]);
return 0;
}
题目:略
题解:
(这题好啊)*最小交换次数就是最后序列的关于原位置的逆序对*,然后我们可以证明最后数列是一个先增后减的样子。(好不容易遇到一个我会证的,当然要证一证)。如下:首先最大的可以随便放,然后我们考虑他左边的第一个数
总结:这道题最大的转点在于:最小交换次数就是最后序列的关于原位置的逆序对这一结论,一定要记熟。
代码:(压了行的,别打我)
#include<cstdio>
#include<algorithm>
using namespace std;const int maxn = 300010;
inline int read(){ int x=0; char ch=getchar();
while (ch<'0' || ch>'9') ch=getchar();
while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
return x;}int c[maxn],n,answer = 0;struct gra
{int h,id;bool operator < (const gra &d)
const {return h > d.h;}}a[maxn];int lowbit(int x)
{return x& (-x);}int query(int x){int ans = 0 ;
while (x){ans+=c[x] ;
x-=lowbit(x);}return ans;}
void update(int x,int val)
{while (x <= n){c[x]+=val;
x +=lowbit(x);}}int main()
{n=read();for (int i = 1;i <= n ;i ++)
{a[i].h=read();a[i].id = i;}
sort(a + 1,a + 1 + n);int head=1; long long ans=0;
for (int i=1; i<=n; i++){if (a[i].h!=a[i-1].h)
while (head<i) update(a[head++].id , 1); int tmp=query(a[i].id);
ans+=min(tmp,head-1-tmp);}printf("%lld",ans);return 0;}
(数据小点还可以dp的,气)。这个我实在是看的别人的,这里推荐一个写得好的:
http://m.blog.csdn.net/FromATP/article/details/64133191
代码:
#include<cstdio>
#include<algorithm>
const int size = 100010;
using namespace std;
int c[size] , n ,w ,y[size],f[size],answ=0,cnt;
struct th{
int v,t,loc,w1,w2;
bool operator < (const th &de) const{
if (w1==de.w1) return w2<de.w2;return w1<de.w1;
}
}ob[size];
int lowbit(int x){return (x&(-x));}
int query(int x)
{ int ans=0;
while (x)
{ans = max(c[x],ans);
x -= lowbit(x);}
return ans;
}
void update(int x,int val)
{ while (x<=n)
{c[x] = max(c[x],val);
x+=lowbit(x);
}}
int main()
{
scanf("%d%d",&w,&n);
for (int i = 1;i<=n;i++){
scanf("%d%d%d",&ob[i].t,&ob[i].loc,&ob[i].v);
ob[i].w1 = 2 * ob[i].t + ob[i].loc;
ob[i].w2 = 2 * ob[i].t - ob[i].loc;
y[++cnt] = ob[i].w2;
}
sort(y + 1 , y + 1 + n);
cnt = unique (y + 1 , y + 1 + n) - y - 1;
for (int i = 1;i<=n;i++) ob[i].w2 = lower_bound(y+1,y+1+cnt,ob[i].w2)-y;
sort(ob + 1,ob + 1 + n);
for (int i = 1;i<=n;i++)
{
f[i] = ob[i].v + query(ob[i].w2);
answ = max(answ,f[i]);
update(ob[i].w2 , f[i]);
}
//for (int i =1;i<=n;i++)
printf("%d",answ);
return 0;
}
结语
树状数组大法好,好写又好调!!!