计蒜客 2018 ICPC宁夏 Continuous Intervals(线段树)

Lamis is a smart girl.She is interested in problems about sequences and their intervals.

Here she shows you a sequence of length nnn with positive integers, denoted by a1,a2,a3,⋯,ana_1, a_2, a_3, \cdots, a_na1​,a2​,a3​,⋯,an​.She is amazed at those intervals, which is a consecutive subsequence of a1,a2,⋯,ana_1,a_2,\cdots,a_na1​,a2​,⋯,an​, with several continuous numbers, and names them continuous intervals.

More precisely, consider a interval al,al+1,⋯,ar−1,ara_l,a_{l+1},\cdots,a_{r-1},a_ral​,al+1​,⋯,ar−1​,ar​ for instance where 1≤l≤r≤n1\le l\le r\le n1≤l≤r≤n. If, after sorting the interval, the difference of any two neighbouring items is less than or equal to 111, the interval will be considered as continuous.

As her best friends, you came from far and wide and travelled thousands of miles to Ningxia, to help her count the number of continuous intervals of the sequence.

Input Format

The input contains several test cases, and the first line is a positive integer TTT indicating the number of test cases which is up to 100010001000.

For each test case, the first line contains an integer n (1≤n≤105)n~(1 \leq n \leq 10^5)n (1≤n≤105) which is the length of the given sequence.The second line contains nnn integers describing all items of the sequence, where the iii-th one is denoted by ai (1≤ai≤109)a_i~(1 \leq a_i \leq 10^9)ai​ (1≤ai​≤109).

We guarantee that the sum of nnn in all test cases is up to 10610^6106.

Output Format

For each test case, output a line containing Case #x: y, where xxx is the test case number starting from 111, and yyy is the number of continuous intervals in this test case.

样例输入

2
4
1 2 1 2
4
1 3 2 4

样例输出

Case #1: 10
Case #2: 8

题目来源

The 2018 ACM-ICPC Chinese Collegiate Programming Contest

 

 

大致题意:定义连续区间,即满足区间最大值与最小值之差加一恰好等于区间数字个数的区间,是连续区间。现在给你一个数列,问你这个数列的所有子区间中有多少个连续区间。

对连续区间进行量化,有区间的max-min+1==cnt,其中cnt表示区间中数字的种类数。注意到,对于任意一个区间,恒有max-min+1>=cnt,我们对式子变形,有max-min-cnt>=-1。因此,如果我们维护每一个区间的max-min-cnt的最小值,当这个最小值为-1的时候,说明存在有连续区间,这个最小值出现的次数就是连续区间的个数。那么问题就是如何维护所有区间的最小值。

注意到,维护max-min-cnt,这里面有一个cnt。根据以前的经验,遇到区间数字种类的问题,一般都是枚举右端点R,然后线段树维护1..L,表示以每一个位置为左端点的区间的数值。同样这题我们也可以利用这种方法,枚举右端点,然后更新,看看有多少个最小值为-1的区间,累积求和就是最后的解。

那么,就是要看怎么更新了。对于最大值,我们可以维护一个单调栈,如果当前新加入的数字比栈顶元素大,那么从栈顶元素出现的位置开始到新加入的数字之前进行修改,增加上二者的差值,然后退栈,继续与前一个比较更新。最小值也是类似,不一一赘述了。然后就是cnt,这个就是常规操作,对于区间[last[x],i]进行修改即可。最后线段树维护区间最小值以及区间最小值的数目,这个也很容易实现。具体见代码:


#include<bits/stdc++.h>
#define LL long long
#define N 100010

using namespace std;

typedef pair<int,int> PII;
map<int,int> last;
int a[N],t1,t2,n;
PII x[N],y[N];

struct ST
{
	struct node{int min,t,lazy,l,r;} tree[N<<2];

	inline void build(int i,int l,int r)
	{
		tree[i]=node{0,0,0,l,r};
		if (l==r) {tree[i].t=1; return;}
		int mid=(l+r)>>1;
		build(i<<1,l,mid);
		build(i<<1|1,mid+1,r);
	}

	inline void push_down(int i)
	{
		tree[i<<1].lazy+=tree[i].lazy;
		tree[i<<1|1].lazy+=tree[i].lazy;
		tree[i<<1].min+=tree[i].lazy;
		tree[i<<1|1].min+=tree[i].lazy;
		tree[i].lazy=0;
	}

	inline void update(int i,int l,int r,LL x)
	{
		if (tree[i].l==l&&tree[i].r==r)
		{
			tree[i].lazy+=x;
			tree[i].min+=x;
			return;
		}
		if (tree[i].lazy!=0) push_down(i);
		int mid=(tree[i].l+tree[i].r)>>1;
		if (mid>=r) update(i<<1,l,r,x);
		else if (mid<l) update(i<<1|1,l,r,x);
		else
		{
			update(i<<1,l,mid,x);
			update(i<<1|1,mid+1,r,x);
		}
		tree[i].min=min(tree[i<<1].min,tree[i<<1|1].min);
		if (tree[i<<1].min==tree[i<<1|1].min) tree[i].t=tree[i<<1].t+tree[i<<1|1].t;
		else if (tree[i<<1|1].min==tree[i].min) tree[i].t=tree[i<<1|1].t;
		else if (tree[i<<1].min==tree[i].min) tree[i].t=tree[i<<1].t;
	}

	inline PII getmin(int i,int l,int r)
	{
		if ((tree[i].l==l)&&(tree[i].r==r)) return PII(tree[i].min,tree[i].t);
		if (tree[i].lazy!=0) push_down(i);
		int mid=(tree[i].l+tree[i].r)>>1;
		if (mid>=r) return getmin(i<<1,l,r);
		else if (mid<l) return getmin(i<<1|1,l,r);
		else
        {
            PII A=getmin(i<<1,l,mid);
            PII B=getmin(i<<1|1,mid+1,r);
            if (A.first==B.first) return PII(A.first,A.second+B.second);
            return min(A,B);
        }
	}

} seg;

int main()
{
    int T_T,T=0;
    scanf("%d",&T_T);
    while(T_T--)
    {
        LL ans=0;
        last.clear();
        scanf("%d",&n);
        seg.build(1,1,n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        t1=t2=0;
        for(int i=1;i<=n;i++)
        {
            while(t1&&a[i]>x[t1].first)
            {
                seg.update(1,x[t1-1].second+1,x[t1].second,a[i]-x[t1].first);
                t1--;
            }
            x[++t1]=PII(a[i],i);
            while(t2&&a[i]<y[t2].first)
            {
                seg.update(1,y[t2-1].second+1,y[t2].second,y[t2].first-a[i]);
                t2--;
            }
            y[++t2]=PII(a[i],i);
            seg.update(1,last[a[i]]+1,i,-1);
            last[a[i]]=i;
            PII res=seg.getmin(1,1,i);
            if (res.first==-1) ans+=res.second;
        }
        printf("Case #%d: %lld\n",++T,ans);
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值