9.22 a.m.小结

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;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值