[洛谷]P4392 [BOI2007]Sound 静音问题 (#线段树)

题目描述

数字录音中,声音是用表示空气压力的数字序列描述的,序列中的每个值称为一个采样,每个采样之间间隔一定的时间。

很多声音处理任务都需要将录到的声音分成由静音隔开的几段非静音段。为了避免分成过多或者过少的非静音段,静音通常是这样定义的:m个采样的序列,该序列中采样的最大值和最小值之差不超过一个特定的阈值c。

请你写一个程序,检测n个采样中的静音。

输入输出格式

输入格式:

第一行有三个整数n,m,c( 1<= n<=1000000,1<=m<=10000, 0<=c<=10000),分别表示总的采样数、静音的长度和静音中允许的最大噪音程度。

第2行n个整数ai (0 <= ai <= 1,000,000),表示声音的每个采样值,每两个整数之间用空格隔开。

输出格式:

列出了所有静音的起始位置i(i满足max(a[i, . . . , i+m−1]) − min(a[i, . . . , i+m−1]) <= c),每行表示一段静音的起始位置,按照出现的先后顺序输出。如果没有静音则输出NONE。

输入输出样例

输入样例#1

7 2 0
0 1 1 2 3 2 2

输出样例#1

2
6

思路

给出n个整数,求长度为m且该区间的最大值与最小值之差<=c的区间数。

看到这种静态区间求最值问题,一般用st表是最快的,所以果断用线段树。

#include <stdio.h>
#include <iostream>
#define ll long long 
#define maxn 1000001
using namespace std;
ll int a[maxn],n,s,m,cnt,c,ans;//a是序列
ll int minx[maxn<<2],maxx[maxn<<2];//区间最小值和最大值
inline ll int leftnode(ll int p) {return p<<1;}//左节点(左儿子) 
inline ll int rightnode(ll int p) {return p<<1|1;}//右节点(右儿子) 
inline void push_up(ll int node)//维护父子节点之间的逻辑关系(合并2个儿子节点) 
{
	minx[node]=min(minx[leftnode(node)],minx[rightnode(node)]);//最小值
	maxx[node]=max(maxx[leftnode(node)],maxx[rightnode(node)]);//最大值
}
void build(ll int node,ll int start,ll int end)//建树,node是当前节点,start和end是范围(是指a数组的范围)
{//线段树自底向上回溯,所以线段树的叶子节点在会被赋值,如果左右区间相同(start==end),说明这是叶子节点 
	if(start==end)
	{
		minx[node]=a[++cnt];//区间最小值
		maxx[node]=a[cnt];//区间最大值
		return; 
	}
	else
	{
		register ll int mid=(start+end)>>1;
		build(leftnode(node),start,mid);
		build(rightnode(node),mid+1,end);//把当前根节点的儿子分别当成新节点,继续建立线段树
		push_up(node);//维护线段树(区间和) 
		//区间最小值与最大值代码类似 
	}
}
ll int min_query(ll int node,ll int start,ll int end,ll int cl,ll int cr)//node是当前节点,start和end是范围(是指a数组的范围),L和R是在区间[L,R]里计算和
{//最小值区间查询
	if(start>=cl && end<=cr)//如果修改的区间包括当前遍历的区间 
	{
		return minx[node];//返回这一区间的区间和 
	}
	register ll int mid=(start+end)>>1,s(99999999);
	if(cl<=mid)
	{
		s=min(s,min_query(leftnode(node),start,mid,cl,cr));
	}
	if(mid<cr)
	{
		s=min(s,min_query(rightnode(node),mid+1,end,cl,cr));
	}
	return s;
}
ll int max_query(ll int node,ll int start,ll int end,ll int cl,ll int cr)//node是当前节点,start和end是范围(是指a数组的范围),L和R是在区间[L,R]里计算和
{//最大值区间查询 
	if(start>=cl && end<=cr)//如果修改的区间包括当前遍历的区间 
	{
		return maxx[node];//返回这一区间的区间和 
	}
	register ll int mid=(start+end)>>1,s(-99999999);
	if(cl<=mid)
	{
		s=max(s,max_query(leftnode(node),start,mid,cl,cr));
	}
	if(mid<cr)
	{
		s=max(s,max_query(rightnode(node),mid+1,end,cl,cr));
	}
	return s;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register ll int i;
	cin>>n>>m>>c;
	for(i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	build(1,1,n);
	for(i=1;i<=n-m+1;i++)//枚举区间起点 
	{
		if(max_query(1,1,n,i,i+m-1)-min_query(1,1,n,i,i+m-1)<=c)//到这一步,真的就可以按题意模拟了 
		{
			ans++;
			cout<<i<<endl;
		}
	}
	if(ans==0)
	{
		cout<<"NONE"<<endl;
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值