[JXOI2017]颜色

题目链接

https://www.luogu.org/problemnew/show/P4065

题目描述

可怜有一个长度为 n 的正整数序列 Ai,其中相同的正整数代表着相同的颜色。

现在可怜觉得这个序列太长了,于是她决定选择一些颜色把这些颜色的所有位置都删去。

删除颜色 i 可以定义为把所有满足 Aj = i 的位置 j 都从序列中删去。

然而有些时候删去之后,整个序列变成了好几段,可怜不喜欢这样,于是她想要知道有多少种删去颜色的方案使得最后剩下来的序列非空且连续。

例如颜色序列 {1, 2, 3, 4, 5},删除颜色 3 后序列变成了 {1, 2} 和 {4, 5} 两段,不满足条件。而删除颜色 1 后序列变成了 {2, 3, 4, 5},满足条件。

两个方案不同当且仅当至少存在一个颜色 i 只在其中一个方案中被删去。

输入输出格式

输入格式:

第一行输入一个整数 T 表示数据组数。每组数据第一行输入一个整数 n 表示数列长度。第二行输入 n 个整数描述颜色序列。

输出格式:

对于每组数据输出一个整数表示答案。

输入输出样例

输入样例#1:
复制

1
5
1 3 2 4 3
输出样例#1: 复制

6

说明

满足条件的删颜色方案有 {1}, {1, 3}, {1, 2, 3}, {1, 3, 4}, {2, 3, 4}, ∅.

对于 20% 的数据,保证 1 ≤∑n ≤ 20。

对于 40% 的数据,保证 1 ≤∑n ≤ 500。

对于 60% 的数据,保证 1 ≤∑n ≤ 10^4。

对于 100% 的数据,保证 1 ≤ T,∑n ≤ 3 × 10^5, 1 ≤ Ai ≤ n。

题解

每段合法区间都是连续的,自然而然会想到枚举一个端点,然后求另一个端点的合法个数。

感觉这个套路比较常见,但是我依然不会。

我们可以先枚举右端点,然后怎么确定左端点的个数呢,感觉很麻烦是不是,正难则反嘛。

定义a[i]为i点的值,head[a[i]]为a[i]最左端点的位置,tail[i]为其最右端的位置。

枚举右端点,当右端点为第i位,令R=i,假设存在一个L,使得区间(L,R)为合法区间,

对于j(L<=j<=R),满足:

1.tail[a[j]]<=R

2.head[a[j]]>=L

到这一步接下来就有点难想到了。

对于每个点i,如果i=tail[a[i]],那么对于任何区间,head[a[i]]+1~tail[a[i]]不可能是左端点,我们将这段区间赋值成一。

再找到最靠近i的k满足,tail[k]>i,则(1,k)之间不可能存在左端点。

i为右端点的贡献等于,i-k-sum[k+1,i](sum[k+1,i]为k+1到i的被标记了的总和,蓝字部分).

至于怎么找k,可以用单调栈来维护,因为弹出栈的话,就不会再用到了。

代码

 #include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 500050 int n,top,a[N],sk[N];
int head[N],tail[N],last[N],nex[N];
struct Tree {int l,r,lazy,sum;}tr[N<<];
template<typename T>void read(T&x)
{
ll k=; char c=getchar();
x=;
while(!isdigit(c)&&c!=EOF)k^=c=='-',c=getchar();
if (c==EOF)exit();
while(isdigit(c))x=x*+c-'',c=getchar();
x=k?-x:x;
}
void read_char(char &c)
{while(!isalpha(c=getchar())&&c!=EOF);}
void push_up(int x)
{
int len=tr[x].r-tr[x].l+;
if (len>)tr[x].sum=tr[x<<].sum+tr[x<<|].sum;
else tr[x].sum=;
if (tr[x].lazy!=-)
tr[x].sum=tr[x].lazy*len;
}
void push_down(int x)
{
if (tr[x].lazy==-)return;
tr[x<<].lazy=tr[x].lazy;
tr[x<<|].lazy=tr[x].lazy;
push_up(x<<);
push_up(x<<|);
tr[x].lazy=-;
}
void bt(int x,int l,int r)
{
tr[x]=Tree{l,r,-,};
if (l==r) return ;
int mid=(l+r)>>;
bt(x<<,l,mid);
bt(x<<|,mid+,r);
}
void update(int x,int l,int r)
{
if (l>r)return;
if (l<=tr[x].l&&tr[x].r<=r)
{
tr[x].lazy=;
push_up(x);
return ;
}
int mid=(tr[x].l+tr[x].r)>>;
push_down(x);
if (l<=mid)update(x<<,l,r);
if (mid<r)update(x<<|,l,r);
push_up(x);
}
int query(int x,int l,int r)
{
if (l<=tr[x].l&&tr[x].r<=r)
return tr[x].sum;
int mid=(tr[x].l+tr[x].r)>>,ans=;
push_down(x);
if (l<=mid)ans=query(x<<,l,r);
if (mid<r)ans+=query(x<<|,l,r);
push_up(x);
return ans;
}
void work()
{
read(n);
for(int i=;i<=n;i++) read(a[i]);
for(int i=;i<=n;i++)
{
if (head[a[i]]==)head[a[i]]=i;
nex[tail[a[i]]]=i;
last[i]=tail[a[i]];
tail[a[i]]=i;
}
bt(,,n);
sk[top=]=;
tail[]=n+;
ll ans=;
for(int i=;i<=n;i++)
{
sk[++top]=i;
while(top&&tail[a[sk[top]]]<=i)top--;
int j=sk[top];
if (tail[a[i]]==i)
{
update(,head[a[i]]+,tail[a[i]]);
ans+=i-j-query(,j+,i);
}
}
printf("%lld\n",ans);
}
void clear()
{
memset(head,,sizeof(head));
memset(tail,,sizeof(tail));
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("aa.in","r",stdin);
#endif
int q;
read(q);
while(q--)
{
clear();
work();
}
}

[JXOI2017]颜色 线段树求点对贡献的更多相关文章

  1. [JXOI2017]颜色 线段树扫描线 + 单调栈

    ---题面--- 题解: 首先题目要求删除一些颜色,换个说法就是要求保留一些颜色,那么观察到,如果我们设ll[i]和rr[i]分别表示颜色i出现的最左边的那个点和最右边的那个点,那么题目就是在要求我们 ...

  2. 洛谷P4065 [JXOI2017]颜色(线段树)

    题意 题目链接 Sol 线段树板子题都做不出来,真是越来越菜了.. 根据题目描述,一个合法区间等价于在区间内的颜色没有在区间外出现过. 所以我们可以对于每个右端点,统计最长的左端点在哪里,刚开始以为这 ...

  3. 2016年湖南省第十二届大学生计算机程序设计竞赛---Parenthesis(线段树求区间最值)

    原题链接 http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1809 Description Bobo has a balanced parenthes ...

  4. UVA 11983 Weird Advertisement --线段树求矩形问题

    题意:给出n个矩形,求矩形中被覆盖K次以上的面积的和. 解法:整体与求矩形面积并差不多,不过在更新pushup改变len的时候,要有一层循环,来更新tree[rt].len[i],其中tree[rt] ...

  5. BNU 2418 Ultra-QuickSort (线段树求逆序对)

    题目链接:http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=2418 解题报告:就是给你n个数,然后让你求这个数列的逆序对是多少?题目中n的范围是n & ...

  6. hdu 1394 (线段树求逆序数)

    <题目链接> 题意描述: 给你一个有0--n-1数字组成的序列,然后进行这样的操作,每次将最前面一个元素放到最后面去会得到一个序列,那么这样就形成了n个序列,那么每个序列都有一个逆序数,找 ...

  7. xdoj-1324 (区间离散化-线段树求区间最值)

    思想 : 1 优化:题意是覆盖点,将区间看成 (l,r)转化为( l-1,r) 覆盖区间 2 核心:dp[i]  覆盖从1到i区间的最小花费 dp[a[i].r]=min (dp[k])+a[i]s; ...

  8. 4163 hzwer与逆序对 (codevs + 权值线段树 + 求逆序对)

    题目链接:http://codevs.cn/problem/4163/ 题目:

  9. poj2299 Ultra-QuickSort(线段树求逆序对)

    Description In this problem, you have to analyze a particular sorting algorithm. The algorithm proce ...

随机推荐

  1. requests接口测试——身份认证

    当调用者访问接口时需要进行用户认证(用户名密码的登录),只有通过了认证才允许调用者访问接口. 1.基本身份认证 许多要求身份认证的web服务都接受HTTP Basic Auth.这是最简单的一种身份认 ...

  2. phpstorm 7.1 注册码

    phpstorm 7.1 注册码 EMBRACE ===== LICENSE BEGIN =====97393-12042010000004TIOFWznalqlK"vkzc2ilc8uub ...

  3. swarm调度

    Swarm filters Configure the available filters 过滤器分为两类,即节点过滤器和容器配置过滤器. 节点过滤器对Docker主机的特性或Docker守护程序的配 ...

  4. 新手C#面向对象的学习2018.08.06

    class Person//声明一个Person类 { //类中的声明与Main中不同,类中声明的是字段而不是函数. public string gender; public string name= ...

  5. C++实现:把一个文件夹里的冗余文件(.txt)删除

    代码很简单,调用了MFC里的几个函数.这里的冗余判断,是要遍历文件内容,进行两两比较. 需要注意的地方有两点: 1.源文件里头文件<afx.h>必须放在最前面.这里是为了避免nafxcwd ...

  6. 138. Copy List with Random Pointer (Graph, Map; DFS)

    A linked list is given such that each node contains an additional random pointer which could point t ...

  7. VS2015解决方案资源管理器空白,不显示内容

    解决方法: 1.先关闭vs: 2.把C:/Users/<users name>/AppData/Local/Microsoft/VisualStudio/14.0/ComponentMod ...

  8. jquery 替换节点实例

    描述: 要求用户选择一个自己不喜欢的商品 说明: 左边是商品列表,右面显示用户不喜欢的商品. 首先用选择器获得用户点击的元素,然后替换右面用户选择的不喜欢的商品. <!doctype html& ...

  9. MongoDB C# 驱动教程

    C# 驱动版本 v1.6.x 本教程基于C#驱动 v1.6.x . Api 文档见此处: http://api.mongodb.org/csharp/current/. 简介 本教程介绍由10gen支 ...

  10. Zookeeper 源码(三)Zookeeper 客户端源码

    Zookeeper 源码(三)Zookeeper 客户端源码 Zookeeper 客户端主要有以下几个重要的组件.客户端会话创建可以分为三个阶段:一是初始化阶段.二是会话创建阶段.三是响应处理阶段. ...