T1:问题 A: 校门外的树
题目描述
校门外有很多树,学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两种操作:
1)K=1,读入 l,r 表示在 l 到 r 之间种上一种树,每次操作种的树的种类都不同;
2)K=2,读入 l,r 表示询问 l 到 r 之间有多少种树。
注意:每个位置都可以重复种树。
输入
第一行 n,m 表示道路总长为 n,共有 m 个操作;
接下来 m 行为 m 个操作。
输出
对于每个 k=2 输出一个答案。
样例输入
5 4
1 1 3
2 2 5
1 2 4
2 3 5
样例输出
1
2
提示
【数据范围与提示】
对于 20% 的数据,1≤n,m≤100;
对于 60% 的数据,1≤n≤103 ,1≤m≤5×104 ;
对于 100% 的数据,1≤n,m≤5×104 ,保证 l,r>0。
题解
这道题已经是第三次做到了。准确的说是“这类题”,之前做的都是离线的,现在变成在线了。之前使用的树状数组(现在这个不知道可不可以),用着还不错。现在学了线段树,发现更简单,因为可以区间修改!
这道题的特殊之处在于,每一种树都是连续的一段。因此在一个区间内,左端点在其内的一定会使种数+1.同时,有些树区间的左端点在外面,但是右端点在里面,如果一个一个的判断太慢了。不妨这么想:对于一个区间[l,r],当l=r时直接看l上面经过了多少棵树就可以了,,因为不存在两种同样的树在同一个位置。现在来看l不等于r的情况,自然,所有经过l的树都可以计入答案,然后对于l+1到r,就不用了O(n)的去找了,直接看这个区间上面有多少个左端点。多一个左端点就多一个答案(可以发现,这样可以处理两种树,左端点在l之后的和左端点小于等于l的),这样就可以用log的效率解决每一个回答。O(nlogn)对于本题而言已经足够了。
注意,线段树中存2个东西,一个是区间内左端点个数(单点修改,区间查询),一个是单点上面树的总数(区间修改,单点查询)。
参考代码
#include<cstdio>
#define lc (x<<1)
#define rc (x<<1|1)
#define MAXN 400100
using namespace std;
struct tree
{
int l,r,sum,lazy,sus,lazy2;
}ask[MAXN];
int n,m;
void make_tree(int x,int l,int r)
{
ask[x].l=l;ask[x].r=r;
ask[x].lazy=0;
if(l==r)
{
ask[x].sum=ask[x].sus=0;
return;
}
int mid=(l+r)/2;
make_tree(lc,l,mid);
make_tree(rc,mid+1,r);
ask[x].sum=ask[lc].sum+ask[rc].sum;
ask[x].sus=ask[lc].sus+ask[rc].sus;
}
void pushdown(int x)
{
if(ask[x].lazy)
{
ask[lc].lazy+=ask[x].lazy;
ask[lc].sum+=ask[x].lazy*(ask[lc].r-ask[lc].l+1);
ask[rc].lazy+=ask[x].lazy;
ask[rc].sum+=ask[x].lazy*(ask[rc].r-ask[rc].l+1);
ask[x].lazy=0;
}
}
void pushdown2(int x)
{
if(ask[x].lazy2)
{
ask[lc].lazy2+=ask[x].lazy2;
ask[lc].sus+=ask[x].lazy2*(ask[lc].r-ask[lc].l+1);
ask[rc].lazy2+=ask[x].lazy2;
ask[rc].sus+=ask[x].lazy2*(ask[rc].r-ask[rc].l+1);
ask[x].lazy2=0;
}
}
void update(int x,int l,int r,int k)
{
if(ask[x].l>r||ask[x].r<l) return;
if(ask[x].l>=l&&ask[x].r<=r)
{
ask[x].sum+=k*(ask[x].r-ask[x].l+1);
ask[x].lazy+=k;
return;
}
pushdown(x);
update(lc,l,r,k);
update(rc,l,r,k);
ask[x].sum=ask[lc].sum+ask[rc].sum;
}
void update2(int x,int l,int r,int k)
{
if(ask[x].l>r||ask[x].r<l) return;
if(ask[x].l>=l&&ask[x].r<=r)
{
ask[x].sus+=k*(ask[x].r-ask[x].l+1);
ask[x].lazy2+=k;
return;
}
pushdown2(x);
update2(lc,l,r,k);
update2(rc,l,r,k);
ask[x].sus=ask[lc].sus+ask[rc].sus;
}
int query(int x,int l,int r)
{
if(ask[x].l>r||ask[x].r<l) return 0;
if(ask[x].l>=l&&ask[x].r<=r) return ask[x].sum;
pushdown(x);
return query(lc,l,r)+query(rc,l,r);
}
int query2(int x,int l,int r)
{
if(ask[x].l>r||ask[x].r<l) return 0;
if(ask[x].l>=l&&ask[x].r<=r) return ask[x].sus;
pushdown2(x);
return query2(lc,l,r)+query2(rc,l,r);
}
int main()
{
scanf("%d%d",&n,&m);
make_tree(1,1,n+1);
while(m--)
{
int pd,l,r;
scanf("%d%d%d",&pd,&l,&r);
if(pd==1)
{
update(1,l,r,1);
update2(1,l,l,1);
}
else
{
if(l<r)
printf("%d\n",query(1,l,l)+query2(1,l+1,r));
else
printf("%d\n",query(1,l,l));
}
}
return 0;
}
T2:问题 B: 堆蛋糕
题目描述
其实小布是一个十分犀利的蛋糕师。他最喜欢的食物就是蛋糕。
一天,他自己做出了N个圆柱状的蛋糕,每个蛋糕都有一个底面圆的半径Ri。高度都是一样的。
小布在开始享用他的蛋糕大餐之前忽然觉得,圆柱状的蛋糕没有什么诱惑力。小布看到了别人结婚用的蛋糕都是很多很多层的,那样的蛋糕才比较给力。但是堆太多层的蛋糕比较困难,于是小布想要堆出许多三层的蛋糕,再开始自己的蛋糕大餐。
当然,作为蛋糕师,小布在堆蛋糕的时候不会对蛋糕的形状有任何破坏,而且,小布希望三层蛋糕的半径从上往下严格递增。这才是一个普通的好蛋糕。
但是小布在考虑一个十分重要的问题,最多可以堆出多少三层蛋糕呢?
输入
输入第一行仅包含一个整数N,表示蛋糕的数量。
接下来N个整数,表示每个蛋糕半径的大小Ri。
N<=3,000,000 Ri<=N
输出
输出一行仅包含一个整数,表示最多可以做成多少个蛋糕
样例输入
6
1 2 3 4 3 2
样例输出
2
题解
这道题很有意思,专门卡我那种贪心。首先桶排序存一下,然后从上到下挨着来,这样贪心明显是有问题的。比如5,4,2,2,2,2,1,1,1,1.如果从上到下就是一种5.4.2,但其实可以2种:5,2,1和4,2,1.同样的5,5,5,5,5,4,4,4,4,4,2,1也会出现这样的情况。因此非常卡这种贪心。自然,还有“那种贪心”。可以发现,贪心之所以失败了,是因为5,4,2,1的个数差太大了。就很容易想到,先把个数多的给选了,剩下的行就行,不行就不行。这样做的好处在于,不会出现“浪费的情况”,这种方式虽然可能取的数不一样,但是能够保证最大利用率,也就能得到正确答案了。因此直接用优先队列来维护。
参考代码
#include<cstdio>
#include<queue>
using namespace std;
struct node
{
int val,num;
};
node a[3001000];
priority_queue<node>q;
bool operator<(node m,node n)
{ return m.val<n.val; }
int n,ans=0;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int k;scanf("%d",&k);
a[k].num=k;a[k].val++;
}
for(int i=1;i<=n;i++)
if(a[i].num==i) q.push(a[i]);
while(q.size()>=3)
{
node u=q.top();q.pop();
node v=q.top();q.pop();
node d=q.top();q.pop();
ans++;
u.val--;v.val--;d.val--;
if(u.val>0) q.push(u);
if(v.val>0) q.push(v);
if(d.val>0) q.push(d);
}
printf("%d",ans);
return 0;
}
T4:问题 D: 清点人数
题目描述
NK 中学组织同学们去五云山寨参加社会实践活动,按惯例要乘坐火车去。由于 NK 中学的学生很多,在火车开之前必须清点好人数。
初始时,火车上没有学生。当同学们开始上火车时,年级主任从第一节车厢出发走到最后一节车厢,每节车厢随时都有可能有同学上下。年级主任走到第 mm 节车厢时,他想知道前 mm 节车厢上一共有多少学生,但是他没有调头往回走的习惯。也就是说每次当他提问时,mm 总会比前一次大。
输入
第一行两个整数 n,k,表示火车共有 n 节车厢以及 k 个事件。
接下来有 k 行,按时间先后给出 k 个事件,每行开头都有一个字母 A,B 或 C。
如果字母为 A,接下来是一个数 m,表示年级主任现在在第 m 节车厢;
如果字母为 B,接下来是两个数 m,p,表示在第 m 节车厢有 p 名学生上车;
如果字母为 C,接下来是两个数 m,p,表示在第 m 节车厢有 p 名学生下车。
学生总人数不会超过 105 。
输出
对于每个 A ,输出一行,一个整数,表示年级主任的问题的答案。
样例输入
10 7
A 1
B 1 1
B 3 1
B 4 1
A 2
A 3
A 10
样例输出
0
1
2
3
提示
【数据范围与提示】
对于 30% 的数据,1≤n,k≤104 ,至少有 3000 个 A;
对于 100% 的数据,1≤n≤5×105 ,1≤k≤105 ,至少有 3×104 个 A。
题解
这道题不是考线段树就是考能够处理区间的线段树。这样还有什么好说的吗。单点修改区间查询的板子。
参考代码
#include<cstdio>
#define lc (x<<1)
#define rc (x<<1|1)
#define MAXN 1000100
using namespace std;
struct tree
{
int l,r,sum,lazy;
}ask[MAXN*4];
int n,m;char s[5];
void make_tree(int x,int l,int r)
{
ask[x].l=l;ask[x].r=r;
ask[x].lazy=0;
if(l==r)
{
ask[x].sum=0;
return;
}
int mid=(l+r)/2;
make_tree(lc,l,mid);
make_tree(rc,mid+1,r);
ask[x].sum=ask[lc].sum+ask[rc].sum;
}
void pushdown(int x)
{
if(ask[x].lazy)
{
ask[lc].lazy+=ask[x].lazy;
ask[lc].sum+=ask[x].lazy*(ask[lc].r-ask[lc].l+1);
ask[rc].lazy+=ask[x].lazy;
ask[rc].sum+=ask[x].lazy*(ask[rc].r-ask[rc].l+1);
ask[x].lazy=0;
}
}
void update(int x,int l,int r,int k)
{
if(ask[x].l>r||ask[x].r<l) return;
if(ask[x].l>=l&&ask[x].r<=r)
{
ask[x].sum+=k*(ask[x].r-ask[x].l+1);
ask[x].lazy+=k;
return;
}
pushdown(x);
update(lc,l,r,k);
update(rc,l,r,k);
ask[x].sum=ask[lc].sum+ask[rc].sum;
}
int query(int x,int l,int r)
{
if(ask[x].l>r||ask[x].r<l) return 0;
if(ask[x].l>=l&&ask[x].r<=r) return ask[x].sum;
pushdown(x);
return query(lc,l,r)+query(rc,l,r);
}
int main()
{
scanf("%d%d",&n,&m);
make_tree(1,1,n);
while(m--)
{
scanf("%s",s);
if(s[0]=='A')
{
int k;
scanf("%d",&k);
printf("%d\n",query(1,1,k));
}
else if(s[0]=='B')
{
int pos,k;
scanf("%d%d",&pos,&k);
update(1,pos,pos,k);
}
else
{
int pos,k;
scanf("%d%d",&pos,&k);
update(1,pos,pos,-k);
}
}
return 0;
}
T5:问题 E: 打鼹鼠
题目描述
在这个“打鼹鼠”的游戏中,鼹鼠会不时地从洞中钻出来,不过不会从洞口钻进去(鼹鼠真胆大……)。洞口都在一个大小为n(n<=1024,注意不是10的24次方)的正方形中。这个正方形在一个平面直角坐标系中,左下角为(0,0),右上角为(n-1,n-1)。洞口所在的位置都是整点,就是横纵坐标都为整数的点。而SuperBrother也不时地会想知道某一个范围的鼹鼠总数。这就是你的任务。
输入
输入有多行。
第一行,一个数n,表示鼹鼠的范围。
以后每一行开头都有一个数m,表示不同的操作:
m=1,那么后面跟着3个数x,y,k(0<=x,y<n),表示在点(x,y)处新出现了k只鼹鼠;
m=2,那么后面跟着4个数x1,y1,x2,y2(0<=x1<=x2<n,0<=y1<=y2<n),表示询问矩形(x1,y1)-(x2,y2)内的鼹鼠数量;
m=3,表示老师来了,不能玩了。保证这个数会在输入的最后一行。
询问数不会超过10000,鼹鼠数不会超过maxlongint。
输出
对于每个m=2,输出一行数,这行数只有一个数,即所询问的区域内鼹鼠的个数。
样例输入
4
1 2 2 5
2 0 0 2 3
3
样例输出
5
题解
这个题就不是板子了,还有点难啃。一个简单的办法是二维前缀和(看了大佬的详解才知道二维的树状数组是这么写的),考试时没想到什么好方法,因此对于每一行维护了一个树状数组,然后把规定的列单独加起来。其实后来想了一想,就是先处理单行的,然后处理把每一行作为一个数,再进行树状数组的存储与更改(绝对不是面积的1,4,9),如此就是2个for循环来维护。现在给出原始的代码。
参考代码
#include<cstdio>
#define LL long long
using namespace std;
int n;
LL c[1025][1025],d[1025][1025];
void update(int x,int y,LL k)
{
for(;y<=n;y+=y&-y) c[x][y]+=k;
}
LL query(int x,int y)
{
LL ret=0;
for(;y;y-=y&-y) ret+=c[x][y];
return ret;
}
int main()
{
scanf("%d",&n);
while(1)
{
int pd;
scanf("%d",&pd);
if(pd==3) break;
if(pd==1)
{
int x,y;LL k;
scanf("%d%d%lld",&x,&y,&k);
update(x+1,y+1,k);
}
if(pd==2)
{
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x1++;y1++;x2++;y2++;
LL ans=0;
for(int i=x1;i<=x2;i++)
ans+=query(i,y2)-query(i,y1-1);
printf("%lld\n",ans);
}
}
return 0;
}
T6:问题 F: 数列区间最大值
题目描述
输入一串数字,给你 M 个询问,每次询问就给你两个数字 X, Y,要求你说出 X 到 Y 这段区间内的最大数。
输入
第一行两个整数 N,M 表示数字的个数和要询问的次数;
接下来一行为 N 个数;
接下来 M 行,每行都有两个整数 X,Y。
输出
输出共 M 行,每行输出一个数。
样例输入
10 2
3 2 4 5 6 8 1 2 9 7
1 4
3 8
样例输出
5
8
提示
【数据范围与提示】
对于全部数据,1≤N≤105 ,1≤M≤106 ,1≤X≤Y≤N。数字不超过 C/C++ 的 int 范围。
题解
如果还用线段树,就会TLE。注意m的数据范围,10的6次方,再乘以一个log,明显过不了。因此需要一个神器:st表。即可O(nlogn)预处理,然后一共O(m)(平均O(1))的回答询问。St表的核心思想在于倍增。一个简单的定理,对于一个区间[L,R],总能分成一个或两个(可有交集)最大的2的k倍的区间长度。因此预处理的f函数,一个代表左端点,一个代表区间长度为2的k次方(存的k,不超过31),因此会有O(nlongn)空间的消耗。这样就可以O(1)回答。具体细节可参照st表和下面的代码。
参考代码
#include<cstdio>
#define LL long long
using namespace std;
LL lg[30],d[200200],f[200100][31];
int n,m;LL a[200100];
LL max1(LL p,LL q)
{ return p>q?p:q; }
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
lg[0]=1;
for(int i=1;i<=32;i++) lg[i]=lg[i-1]*2ll;
for(int i=2;i<=200000;i++) d[i]=d[i/2]+1;
for(int i=1;i<=n;i++) f[i][0]=a[i];
for(int i=1;i<=d[n];i++)
for(int j=1;j+lg[i]-1<=n;j++)
f[j][i]=max1(f[j][i-1],f[j+lg[i-1]][i-1]);
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",max1(f[l][d[r-l+1]],f[r-lg[d[r-l+1]]+1][d[r-l+1]]));
}
return 0;
}