您的位置:首页 > 其它

3.4 日常(树状数组&线段树)

2017-03-04 08:51 375 查看
今天依旧练树状数组..

Counting Black(POJ - 1656)

题目大意:给定100*100的矩阵,三种操作,涂白色,涂黑色,统计区间内黑块的个数。

首先看到这道题的数据规模,嗯都非常小。考虑一下暴力,大概是十的六次方可以轻松过。于是手写一个暴力,过了。

当然我做题是为了锻炼自己的能力,想了一下二维树状数组,要对给定区间中的每一个点进行操作,然后在求和时依旧用求4个矩形来代替。

暴力代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100005
using namespace std;

int T,n,m;
int tree[1007][1007];

void add(int x,int y,int l,int va)
{
for(int i=x;i<=x+l-1;i++)
for(int j=y;j<=y+l-1;j++)
tree[i][j]=va;
}

int sum(int x,int y,int l)
{
int now=0;
for(int i=x;i<=x+l-1;i++)
for(int j=y;j<=y+l-1;j++)
now+=tree[i][j];
return now;
}

int main()
{
scanf("%d",&m);
n=100;
for(int i=1;i<=m;i++)
{
char s[10];
scanf("%s",s);
if(s[0]=='B')
{
int x1,y1,l;
scanf("%d%d%d",&x1,&y1,&l);
add(x1,y1,l,1);
}
else if(s[0]=='W')
{
int x1,y1,l;
scanf("%d%d%d",&x1,&y1,&l);
add(x1,y1,l,0);
}
else if(s[0]=='T')
{
int x,y,l;
scanf("%d%d%d",&x,&y,&l);
printf("%d\n",sum(x,y,l));
}
}
}


二维树状数组代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100005
#define lowbit(x) (x&(-x))
using namespace std;

int T,n,m;
int tree[107][107],flag[107][107];

void add(int x,int y,int va)
{
if(flag[x][y]==va)
return;
flag[x][y]=va;
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=n;j+=lowbit(j))
tree[i][j]+=va;
}

int sum(int x,int y)
{
int now=0;
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
now+=tree[i][j];
return now;
}

int main()
{
scanf("%d",&m);
memset(flag,-1,sizeof flag);
n=100;
for(int i=1;i<=m;i++)
{
char s[10];
scanf("%s",s);
if(s[0]=='B')
{
int x,y,l;
scanf("%d%d%d",&x,&y,&l);
for(int i=x;i<=x+l-1;i++)
for(int j=y;j<=y+l-1;j++)
add(i,j,1);
}
else if(s[0]=='W')
{
int x,y,l;
scanf("%d%d%d",&x,&y,&l);
for(int i=x;i<=x+l-1;i++)
for(int j=y;j<=y+l-1;j++)
add(i,j,-1);
}
else if(s[0]=='T')
{
int x,y,l;
scanf("%d%d%d",&x,&y,&l);
printf("%d\n",sum(x+l-1,y+l-1)-sum(x-1,y+l-1)-sum(x+l-1,y-1)+sum(x-1,y-1));
}
}
}


值得一提的是暴力跑得比树状数组快..

Cows (POJ - 2481 )

题目大意:给定一些区间求这些区间中有多少个是重叠的部分。

这道题有一个非常巧妙的实现方法,可以转换成之前做的一道水题。因为题目中给定了区间了开头结尾,如果我们将这个区间的开头结尾的数看成一个坐标(s,e)那么就可以发现这道题求的是每个点左上角点的个数。就是Star的变换版,但是因为输入数据并不是按照一定顺序给出,所以我们要进行排序处理。将y进行降序排序,y相同x按照升序排序,就这样就变成了Star。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100005
#define lowbit(x) (x&(-x))
using namespace std;

int n;
int tree[MAXN],ans[MAXN];
struct node
{
int x,y;
int cnt;
bool operator < (const node &a) const
{
return y>a.y||(y==a.y&&x<a.x);
}
}a[MAXN];

void add(int x,int va)
{
while(x<=MAXN)
{
tree[x]+=va;
x+=lowbit(x);
}
}

int sum(int x)
{
int now=0;
while(x>0)
{
now+=tree[x];
x-=lowbit(x);
}
return now;
}

int main()
{
while(scanf("%d",&n)&&n)
{
memset(ans,0,sizeof ans);
memset(tree,0,sizeof tree);
memset(a,0,sizeof a);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a[i].x,&a[i].y);
a[i].cnt=i;
a[i].x++,a[i].y++;
}
sort(a+1,a+n+1);
ans[a[1].cnt]=sum(a[1].x);
add(a[1].x,1);
for(int i=2;i<=n;i++)
{
if(a[i-1].x==a[i].x&&a[i-1].y==a[i].y)
ans[a[i].cnt]=ans[a[i-1].cnt];
else
ans[a[i].cnt]=sum(a[i].x);
add(a[i].x,1);
}
for(int i=1;i<n;i++)
printf("%d ",ans[i]);
printf("%d\n",ans
);
}
}


MooFest (POJ - 1990)

题目大意:每个奶牛有两个信息,一个是位置,一个是耳背的等级。两头奶牛之间的交流所需的声音是他们中最大的耳背等级乘以他们距离的绝对值。

首先我们可以肯定这道题用n2的时间肯定过不了,所以我们需要加优化。这就给了我们用树状数组的机会。

首先以耳背程度进行升序排序,这样我x们就只需要考虑当前这头牛x到其他牛的距离之和,而这个和又可以分成两部分。一部分是位置比x小的,另一部分是位置比x大的。而求这个和我们可以用两个树状数组,一个树状数组记录比x小的牛的个数a,另一个记录比x小的牛的位置之和b。

那么对于位置比x小的距离之和就等于a∗va[x]−b,那么位置比x大的也非常好求是sum−b−(i−1−a)∗va[x]其中的sum就是所有距离之和。

这道题的思路不是很好想,但是代码实现却非常的简单,应该算是比较有难度的树状数组题了。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 100005
#define lowbit(x) (x&(-x))
using namespace std;

long long ans;
int n;
long long tree[2][MAXN];
struct node
{
int x,va;
bool operator < (const node &a) const
{
return va<a.va;
}
}a[MAXN];

void add(int x,int va,int wh)
{
while(x<=MAXN)
{
tree[wh][x]+=va;
x+=lowbit(x);
}
}

long long sum(int x,int wh)
{
long long now=0;
while(x>0)
{
now+=tree[wh][x];
x-=lowbit(x);
}
return now;
}

int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i].va,&a[i].x);
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
{
long long a1=sum(a[i].x,0),b=sum(a[i].x,1);
ans+=(a1*a[i].x-b+sum(20000,1)-b-(i-1-a1)*a[i].x)*a[i].va;
add(a[i].x,1,0);
add(a[i].x,a[i].x,1);
}
printf("%lld",ans);
}


影子的宽度

基础的线段树题目,对于每个区间打一个标记是否被全部覆盖,最后遍历一遍线段树就可以。

值得注意的是输入数组是左闭右开以及避免负数的端点将其平移至正数。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

int n,s,e;

struct node
{
int s,e;
bool cover;
node *l,*r;
node()
{
l=r=NULL;
cover=0;
}
}root;

void add(node *now,int x,int y)
{
if(now->cover)
return;
if(now->s==x&&now->e==y)
{
now->cover=1;
return;
}
int mid=(now->s+now->e)/2;
if(y<=mid)
{
if(now->l==NULL)
{
now->l=new node;
now->l->s=now->s;
now->l->e=mid;
}
add(now->l,x,y);
}
else if(x>=mid+1)
{
if(now->r==NULL)
{
now->r=new node;
now->r->s=mid+1;
now->r->e=now->e;
}
add(now->r,x,y);
}
else
{
if(now->l==NULL)
{
now->l=new node;
now->l->s=now->s;
now->l->e=mid;
}
if(now->r==NULL)
{
now->r=new node;
now->r->s=mid+1;
now->r->e=now->e;
}
add(now->l,x,mid);
add(now->r,mid+1,y);
}
}

int num(node *now)
{
if(now->cover)
return now->e-now->s+1;
int ans=0;
if(now->l!=NULL)
ans+=num(now->l);
if(now->r!=NULL)
ans+=num(now->r);
return ans;
}

int main()
{
int XO;
scanf("%d%d%d",&s,&e,&n);
XO=-s;
s+=XO,e+=XO;
root.s=s,root.e=e;
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x+=XO,y+=XO;
add(&root,x,y-1);
}
printf("%d",num(&root));
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息