Sequence(二分 + 线段树)

H-Sequence_2020ICPC 江西省大学生程序设计竞赛(重现赛)@Joanh_Lan (nowcoder.com)

题目描述

 给定一个包含n个整数的数组a,你要对它执行两种类型的m个操作。1.给定两个整数x,y,将索引x的个数替换为数字y,即ax:=y。2.给定一个整数x,打印a的连续子序列的个数,其最小值为ax。它保证数组a在任何时候都没有重复的值。

输入描述:

第一行包含两个整数n,m(1Sn,mS105),其中n是数组的大小,m是要执行的操作的数量。第二行包含n个整数,第i个整数是ai (1SaiS231-1)然后是m行,依次描述m个你要执行的操作。每行以一个整数optE[1,2]开始,表示要执行的操作类型。如果opt=1,则会出现两个整数x, y (1Sxsn,1Sys231-1),如上所述。如果opt=2,一个整数x (1sxsn)紧随其后,如上所述。

输出描述:

对于类型2的每个操作,在一行上打印一个整数作为答案。

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

示例1

输入

复制

10 5
8 3 6 2 10 9 5 7 1 4 
2 2
1 9 11
1 5 12
2 4
1 8 18

输出

复制

4
28

题解:
(反思:写题时越是有思路,越应该仔细推敲,看能不能以更简单的代码实现,本来已经想到二分了,确想了一直及其傻逼的实现,为啥不直接用二分???)!!!

题意很容易理解,看有多少子串最小值是a[p],暴力的解法就是每次向所求的下标左右遍历,直到找到比他小的,这样我们就知道最左和最右的下标l,r,输出(p - l + 1)*(r - l + 1)即可

可是每次遍历的话,时间复杂度肯定会超

所以我们利用线段树可以求每一段最小值,并且可以进行单点修改

那我们如何求左右边界?

利用两次二分,左右check

L,p  p,R是最小值是否a[p]

 

#include <cstdio>
#include <cstring>
#include <algorithm>
#include<iostream>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long ll;
//#define int long long
ll mod = 998244353;
struct node
{
	int l,r;
	int mi;
}tre[400060];
int a[100050];
void pushup(int rt)
{
	tre[rt].mi = min(tre[rt*2].mi,tre[rt*2 + 1].mi);
}
void build(int rt,int l,int r)
{
	tre[rt].l = l;
	tre[rt].r = r;
	if(l == r)
	{
		tre[rt].mi = a[l];
		return ;
	}
	int mid = (l + r)/2;
	build(rt*2,l,mid);
	build(rt*2+1,mid + 1,r);
	pushup(rt);
}
void updata(int rt,int l,int r,int pos)
{
	if(tre[rt].l == l&& tre[rt].r == r)
	{
		tre[rt].mi = pos;
		return;
	}
	
	int mid=(tre[rt].l+tre[rt].r)/2;
	if(l<=mid)
	{
		updata(2*rt,l,r,pos);
	} 
	if(r>mid)
	{
		updata(2*rt+1,l,r,pos);
	}
	pushup(rt);
}
int ask(int rt,int l,int r)
{
	if(tre[rt].l >= l&&tre[rt].r <= r)
	{
		return tre[rt].mi;
	}
	int ans = (1 << 31) - 1;
	int mid = (tre[rt].l + tre[rt].r)/2;
	if(l <= mid)
	{
		ans = min(ans,ask(rt*2,l,r));
	}
	if(r > mid)
	{
		ans = min(ans,ask(rt*2 + 1,l,r));
	}
	return ans;
}
void solve()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++)
	{
		scanf("%d",&a[i]);
	}
	build(1,1,n); 
	while(m--)
	{
		int op;
		scanf("%d",&op);
		if(op == 1)
		{
			int x,pos;
			scanf("%d%d",&x,&pos);
			updata(1,x,x,pos); 
			a[x] = pos;
		}
		else
		{
			int p;
			scanf("%d",&p);
			int l = 1,r = p;
			while(l <= r)
			{
				int mid = (l + r)/2;
				if(ask(1,mid,p) == a[p])
				{
					r = mid - 1;
				}
				else
				{
					l = mid + 1;
				}
			}
			ll L = l;
			l = p,r = n;
			while(l <= r)
			{
				int mid = (l + r)/2;
				if(ask(1,p,mid) == a[p])
				{
					l = mid + 1;
				}
				else
				{
					r = mid - 1;
				}
			}
			ll R = r;
			printf("%lld\n",(ll)(p - L + 1)*(r - p + 1));
		}
	}
}

signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);cout.tie(0);
	int t = 1;
//	cin >> t;
	while(t--)
	{
		solve(); 
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值