9.22am的题解
今天上午考数据结构,下午综合,都不太难,随附题解一篇。
AM
T1 校门外的树
时间限制: 1 Sec 内存限制: 128 MB
题目描述
校门外有很多树,学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两种操作:
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×10 4 ;
对于 100% 的数据,1≤n,m≤5×10 4 ,保证 l,r>0。
本题与HH的项链颇有几分相似,如有需要者 https://www.luogu.com.cn/problem/P1972;
本题O(n^2)是过不去的,我们思考O(nlogn)的做法;
由于询问的是区间中树的种类数,所以问询区间中相同种类的树只有一个会起作用;
询问的右端点有多少个树的左端点,就代表了有可能有多少个树区间处于询问区间内;
而询问的左端点有多少个树的右端点,就代表了上述有多少个树区间实际并不处于询问区间内;(这一点应该好想);
于是答案即为ask(右端点的左括号个数)-ask(左端点的右括号个数);
我们发现修改和查询都可以在树状数组中执行,于是整个算法效率为O(mlogn)。
#include<bits/stdc++.h>
using namespace std;
int op,n,m,a,b;
int c1[50005],c2[50005];
void change(int x,bool flag)
{
while(x<=n)
{
if(!flag)c1[x]++;
else c2[x]++;
x+=x&(-x);
}
}
int ask(int x,bool flag)
{
int ret=0;
while(x)
{
if(!flag)ret+=c1[x];
else ret+=c2[x]
x-=x&(-x);
}
return ret;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
{
scanf("%d%d%d",&op,&a,&b);
if(op==1)
{
change(a,op-1);
change(b,op);
}
else
{
printf("%d\n",ask(b,op-1)-ask(a-1,op));
}
}
}
T2 堆蛋糕
时间限制: 3 Sec 内存限制: 128 MB
其实小布是一个十分犀利的蛋糕师。他最喜欢的食物就是蛋糕。
一天,他自己做出了N个圆柱状的蛋糕,每个蛋糕都有一个底面圆的半径Ri。高度都是一样的。
小布在开始享用他的蛋糕大餐之前忽然觉得,圆柱状的蛋糕没有什么诱惑力。小布看到了别人结婚用的蛋糕都是很多很多层的,那样的蛋糕才比较给力。但是堆太多层的蛋糕比较困难,于是小布想要堆出许多三层的蛋糕,再开始自己的蛋糕大餐。
当然,作为蛋糕师,小布在堆蛋糕的时候不会对蛋糕的形状有任何破坏,而且,小布希望三层蛋糕的半径从上往下严格递增。这才是一个普通的好蛋糕。
但是小布在考虑一个十分重要的问题,最多可以堆出多少三层蛋糕呢?
输入
输入第一行仅包含一个整数N,表示蛋糕的数量。
接下来N个整数,表示每个蛋糕半径的大小Ri。
N<=3,000,000 Ri<=N
输出
输出一行仅包含一个整数,表示最多可以做成多少个蛋糕
样例输入
6
1 2 3 4 3 2
样例输出
2
本题贪心,需用优先队列优化。
策略:每次选择三个出现次数最大的半径,并都减去它们中的最小值,把不为0的入队,直到优先队列size小于3。
#include<bits/stdc++.h>
using namespace std;
int n,a[3000005],x,ans,minn;
int y[4];
priority_queue < int > q;
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%d",&x);
a[x]++;//观察到r较小,可用类似桶排的想法保存信息
}
for(int i=1; i<=3000000; i++)
{
if(a[i])q.push(a[i]);//为啊a[i]=0的被筛掉
}
while(q.size()>=3)
{
y[1]=q.top(),q.pop();
y[2]=q.top(),q.pop();
y[3]=q.top(),q.pop();
minn=min(y[1],min(y[2],y[3]));
ans+=minn;
for(int i=1; i<=3; i++)
{
if(y[i]-minn==0)
{
continue;
}
else q.push(y[i]-minn);
}
}
printf("%d",ans);
}
T3 清点人数
时间限制: 1 Sec 内存限制: 128 MB
题目描述
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。
本题A少,有可能n^2可过,有人过了,但笔者没有试过,故不加定论。
树状数组是可过的。
可以把本题看做树状数组的板。
#include<bits/stdc++.h>
using namespace std;
int n,k,m,p,mm,c[500005];
char op;
int ask(int x)
{
int ret=0;
while(x)
{
ret+=c[x];
x-=x&(-x);
}
return ret;
}
void change(int x,int num)
{
while(x<=n)
{
c[x]+=num;
x+=x&(-x);
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++)
{
scanf("%s",&op);
if(op=='A')
{
scanf("%d",&mm);
printf("%d\n",ask(mm));
}
else if(op=='B')
{
scanf("%d%d",&m,&p);
change(m,p);
}
else
{
scanf("%d%d",&m,&p);
change(m,-p);
}
}
}
/*
10 8
A 1
B 3 1
B 1 4
B 4 1
A 2
A 3
C 1 1
A 10
*/
T4 打鼹(yan)鼠
时间限制: 1 Sec 内存限制: 128 MB
题目描述
在这个“打鼹鼠”的游戏中,鼹鼠会不时地从洞中钻出来,不过不会从洞口钻进去(鼹鼠真胆大……)。洞口都在一个大小为n(n<=1024)的正方形中。这个正方形在一个平面直角坐标系中,左下角为(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
分析
先打三遍鼹鼹鼹,这是过题的关键(划掉
横看像什么二维树状数组,我旁边的老哥已经开始自创算法了…
其实不难。
我们把要求在线问题离线解决,倒序处理。M^2可解。
#include<bits/stdc++.h>
#define MAXN 10001
using namespace std;
int n,cnt;//now,xi,yi,xii,yii;
int ans[MAXN];
struct node
{
int op;
int x,y,k;
int xI,yI,xII,yII;
}q[MAXN];
int main()
{
scanf("%d",&n);
int i=1;
while(true)
{
int op; scanf("%d",&op);
if(op==2) scanf("%d%d%d%d",&q[i].xI,&q[i].yI,&q[i].xII,&q[i].yII),q[i].op=2;
else if(op==1) scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].k),q[i].op=1;
else break;
++i;
}
for(int j=i-1;j>=1;j--)
{
if(q[j].op==2)
{
int nans=0;
for(int r=1;r<j;r++)
{
if(q[r].op==2) continue;
if(q[r].x <= q[j].xII && q[r].x >=q[j].xI )
{
if(q[r].y <= q[j].yII && q[r].y >=q[j].yI )
{
nans+=q[r].k;
}
}
}
ans[++cnt]=nans;
}
}
for(int j=cnt;j>=1;--j)
{
printf("%d\n",ans[j]);
}
return 0;
}
/*
4
1 2 2 5
1 1 8 1
2 0 0 2 5
3
*/
T5 数列区间最大值
时间限制: 1 Sec 内存限制: 128 MB
题目描述
输入一串数字,给你 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 范围。
本题其实有不同的做法,但线段树会被卡,或许是因为其查询不是 O ( 1 ) O(1) O(1) 的(m较大)
故选用ST表,构建 O ( n l o g n ) O(nlogn) O(nlogn),查询 O ( 1 ) O(1) O(1).
#include<iostream>
#include<cstdio>
using namespace std;
int lg[100005],a[100005];
int f[100005][65];
int n,T;
int main()
{
scanf("%d%d",&n,&T);
for(int i=1; i<=n; i++)
{
scanf("%d",a+i);
f[i][0]=a[i];
}
lg[1]=0;
for(int j=2; j<=n; j++)
{
lg[j]=lg[j>>1]+1;
}
int t=lg[n]+1;
for(int i=1; i<t; i++)
{
for(int j=1; j<=n-(1<<i)+1; j++)
{
f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
}
}
for(int i=1; i<=T; i++)
{
int u,v,len;
scanf("%d%d",&u,&v);
len=v-u+1;
t=lg[len];
printf("%d\n",max(f[u][t],f[v-(1<<t)+1][t]));
}
}