Description
校门外有很多树,有苹果树,香蕉树,有会扔石头的,有可以吃掉补充体力的……
如今学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两个操作:
K=1,读入l,r表示在l~r之间种上的一种树
K=2,读入l,r表示询问l~r之间能见到多少种树
(0<l,r)
Input
第一行n,m表示道路总长为n,共有m个操作
接下来m行为m个操作
Output
对于每个k=2输出一个答案
Sample Input
5 4
1 1 3
2 2 5
1 2 4
2 3 5
Sample Output
1
2
Hint
20%的数据保证,n,m<=100
60%的数据保证,n <=1000,m<=50000
100%的数据保证,n,m<=50000
【分析】
这道题是对区间进行操作,自然而然就想到了线段树。但是发现比较麻烦的一点出现了,他是查询每段区间各有多少种树。所以线段树是行不通的。于是就想到了树状数组。我们可以开两个树状数组,分别记录每次区间修改的右端点和左端点。
那么每次查询的答案就是(总的种类数-查询区间的左端点 左边的 修改区间的右端点数-查询区间的右端点 右边的 修改区间的左端点数)但是注意那个数不要包含当前查询区间的端点。这样说有一点绕,我们来举个例子。
设有这样一组数据:添加 (1)1~3 (2) 2~4 (3)3~5 (4)5~6 我们顺序编号树的编号,那么得到这样一列树。
位置 | 1 | 2 | 3 | 4 | 5 | 6 |
第一次修改 | 1 | 1 | 1 | |||
第二次修改 | 2 | 2 | 2 | |||
第三次修改 | 3 | 3 | 3 | |||
第四次修改 | 4 | 4 |
这个答案是怎么求出来的呢,查询区间的左端点为4,他左边的修改区间的右端点数为1,就是第一次修改。而查询区间的右端点为5,他右边的修改区间的左端点数为0;
所以答案是4-1-0,为3;
(PS:表达能力有限,看代码或许容易理解些)
【代码】
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
#define _lowbit(x) (x&(-x))
using namespace std;
int N,M,tot=0;
int cl[50005],cr[50005];
void _in(int &x)
{
char t=getchar();
while(t<'0'||'9'<t) t=getchar();
for(x=t-'0',t=getchar();'0'<=t&&t<='9';x=x*10+t-'0',t=getchar());
}
void _insert(int l,int r)
{
for(int i=l;i<=N;i+=_lowbit(i)) //cl记录左端点个数的前缀和
cl[i]++;
for(int i=r;i<=N;i+=_lowbit(i)) //cr记录右端点个数的前缀和
cr[i]++;
}
int _findl(int l) //找查询区间左边的右端点数
{
int ans=0;
for(int i=l-1;i;i-=_lowbit(i))
ans+=cr[i];
return ans;
}
int _findr(int r) //找查询区间右边的左端点数
{
int ans=0;
for(int i=N;i;i-=_lowbit(i))
ans+=cl[i];
for(int i=r;i;i-=_lowbit(i))
ans-=cl[i];
return ans;
}
void _init()
{
_in(N);_in(M);
}
void _solve()
{
int p,l,r;
for(int i=1;i<=M;i++)
{
_in(p);_in(l);_in(r);
if(p==1)
{
tot++;
_insert(l,r);
}
else
{
int left=_findl(l);
int right=_findr(r);
printf("%d\n",tot-left-right);
}
}
}
int main()
{
_init();
_solve();
return 0;
}