【线段树】【P4062】 [Code+#1]Yazid 的新生舞会
Description
给定一个长度为 \(n\) 的序列,求有多少子区间满足区间众数严格大于区间长度的一半。如果区间有多个出现次数最多且不同的数则取较小的数为众数。
Limitation
对于全部的数据,\(1 \leq n \leq 500000\)
序列中数的值域为 \([0,n)\)
子任务:序列中的数值域为 \([0,7]\)
Solution
考虑如果区间有多个出现次数最多且不同的数,那么这个区间显然是不合法的。于是区间出现多个众数取最小的限制其实没有什么 * 用。
考虑枚举区间众数是 \(x\),将区间中等于 \(x\) 的数置为 \(1\),不等于的置为 \(-1\),考虑 \(x\) 是该区间的众数且出现次数严格大于区间长度一半的充要条件是区间的和大于 \(0\)。
于是考虑问题被转化成了给定一个仅含有 \(-1\) 和 \(1\) 的序列,求有多少子区间满足区间和大于 \(0\)。
对于序列的子区间问题的一个经典套路是通过做前缀和转化成序列点对问题,这是因为子区间有 \(O(n^2)\) 个,而点只有 \(O(n)\) 个,子区间甚至遍历一遍复杂度都难以支持,但是单点只需要在遍历时维护对应信息即可。
考虑对序列做前缀和,那么 \([l,~r]\) 区间是合法的当且仅当 \(sum_r - sum_{l - 1} > 0\),也即 \(sum_r > sum_{l - 1}\)。于是只需要求前缀和序列的顺序对个数即可。
对于序列值域较小的子任务,可以直接枚举众数,然后每次用权值线段树/权值树状数组 \(O(n \log n)\) 去求顺序对个数。时间复杂度 \(O(\max_{i = 1}^{n}\{A_i\} \times n \log n)\)。
考虑枚举这 \(O(n)\) 个众数一共产生 \(O(n)\) 个 \(1,~-1\) 序列,共有 \(O(n^2)\) 个数,在这 \(O(n^2)\) 个数中只有 \(O(n)\) 个 \(1\),剩下全是 \(-1\)。由于可以认为是这 \(O(n)\) 个 \(1\) 和 \(O(n)\) 个空白(即两个相邻序列)将这 \(O(n^2)\) 个 \(-1\) 隔开,于是连续的 \(-1\) 序列有 \(O(n)\) 个。
考虑每段连续的 \(-1\) 序列,假设它们的前缀和值域是 \([l,~r]\),则它们对答案产生的贡献是
\]
其中 \(tree_i\) 为 \(i\) 出现的次数。
于是考虑只需要用权值线段树维护 \(tree_i\) 和 \(tree_i \times i\) 即可。考虑将这连续一段 \(-1\) 插入可以区间 \(+1\)。对于不是 \(-1\) 的位置,显然可以单次 \(O(\log n)\) 计算。于是总复杂度 \(O(n \log n)\)。
Code
#include <cstdio>
#include <vector>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
#define printf(...)
typedef long long int ll;
namespace IPT {
const int L = 1000000;
char buf[L], *front=buf, *end=buf;
char GetChar() {
if (front == end) {
end = buf + fread(front = buf, 1, L, stdin);
if (front == end) return -1;
}
return *(front++);
}
}
template <typename T>
inline void qr(T &x) {
char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
if (lst == '-') x = -x;
}
namespace OPT {
char buf[120];
}
template <typename T>
inline void qw(T x, const char aft, const bool pt) {
if (x < 0) {x = -x, putchar('-');}
int top=0;
do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
while (top) putchar(OPT::buf[top--]);
if (pt) putchar(aft);
}
const int maxn = 500005;
int n;
ll ans;
int MU[maxn];
std::vector<int>num[maxn];
struct Tree {
int l, r;
ll sum;
Tree *ls, *rs;
std::pair<ll, ll> v;
std::pair<int, bool> tag;
Tree(const int _l, const int _r) : l(_l), r(_r), sum(((r - l + 1ll) * (r + l)) >> 1), ls(NULL), rs(NULL) {}
inline void maketag(std::pair<int, bool> v) {
if (v.second) {
this->v.first = this->v.second = this->tag.first = 0;
this->tag.second = true;
}
this->v.first += (r - l + 1ll) * v.first;
this->v.second += v.first * sum;
this->tag.first += v.first;
}
inline void pushdown() {
this->ls->maketag(this->tag);
this->rs->maketag(this->tag);
this->tag.first = this->tag.second = 0;
}
inline void pushup() {
this->v.first = this->ls->v.first + this->rs->v.first;
this->v.second = this->ls->v.second + this->rs->v.second;
}
inline bool InRange(const int l, const int r) {
return (this->l >= l) && (this->r <= r);
}
inline bool OutofRange(const int l, const int r) {
return (this->l > r) || (this->r < l);
}
};
Tree *rot;
void build(Tree *const u);
int calc(const int k, const int p);
void update(Tree *const u, const int l, const int r);
std::pair<ll, ll> query(Tree *const u, const int l, const int r);
int main() {
freopen("1.in", "r", stdin);
qr(n); qr(ans); ans = 0;
for (int i = 1; i <= n; ++i) {
qr(MU[i]); num[MU[i]].push_back(i);
}
build(rot = new Tree(-n, n));
for (int k = 0; k < n; ++k) {
rot->maketag(std::make_pair(0, true));
int l = 0, cnt = 0;
for (auto u : num[k]) {
if (u != l) {
int r = u - 1;
int ar = calc(cnt, l), al = calc(cnt, r);
auto ret1 = query(rot, -n, al - 1), ret2 = query(rot, al, ar);
ans += ret1.first * (ar - al + 1) + ret2.first * ar - ret2.second;
update(rot, al, ar);
printf("QWAQ%d %d %lld\n", k, u, ans);
}
int v = calc(++cnt, u);
ans += query(rot, -n, v - 1).first;
printf("QWAQ%d %d %lld\n", k, u, ans);
update(rot, v, v);
l = u + 1;
}
if (l <= n) {
int r = n;
int ar = calc(cnt, l), al = calc(cnt, r);
auto ret1 = query(rot, -n, al - 1), ret2 = query(rot, al, ar);
printf("EMM%lld %lld %lld %lld\n", ret1.first, ret1.second, ret2.first, ret2.second);
ans += ret1.first * (ar - al + 1) + ret2.first * ar - ret2.second;
}
printf("QWQ%d %lld\n", k, ans);
}
qw(ans, '\n', true);
return 0;
}
void update(Tree *const u, const int l, const int r) {
if (u->InRange(l, r)) {
u->maketag(std::make_pair(1, false));
} else if (!u->OutofRange(l, r)) {
u->pushdown();
update(u->ls, l, r); update(u->rs, l, r);
u->pushup();
}
printf("UPD%d %d %d %d %d %d\n", u->l, u->r, l, r, u->v.first, u->v.second);
}
std::pair<ll, ll> query(Tree *const u, const int l, const int r) {
printf("EMMQAQ%d %d %d %d %d\n", u->l, u->r, l, r, u->v.first);
if (u->InRange(l, r)) {
return u->v;
} else if (u->OutofRange(l, r)) {
return std::make_pair(0ll, 0ll);
} else {
u->pushdown();
auto rl = query(u->ls, l, r), rr = query(u->rs, l, r);
return std::make_pair(rl.first + rr.first, rl.second + rr.second);
u->pushup();
}
}
inline int calc(const int k, const int p) {
return (k << 1) - p;
}
void build(Tree *const u) {
if (u->l == u->r) return;
int mid = (u->l + u->r) >> 1;
build(u->ls = new Tree(u->l, mid)); build(u->rs = new Tree(mid + 1, u->r));
}
Summary
对于子区间问题,可以通过做前缀和转化成点对问题。
【线段树】【P4062】 [Code+#1]Yazid 的新生舞会的更多相关文章
- luogu P4062 [Code+#1]Yazid 的新生舞会(线段树+套路)
今天原来是平安夜啊 感觉这题是道好题. 一个套路枚举权值\(x\),把权值等于\(x\)的设为1,不等于的设为-1,然后问题转化为多少个区间权值和大于. 发现并不是很好做,还有一个套路,用前缀和查分来 ...
- 洛谷 P4062 - [Code+#1]Yazid 的新生舞会(权值线段树)
题面传送门 题意: 给出一个序列 \(a\),求 \(a\) 有多少个子区间 \([l,r]\),满足这个区间中出现次数最多的数出现次数 \(>\dfrac{r-l+1}{2}\) \(1 \l ...
- 洛谷 P4062 - [Code+#1]Yazid 的新生舞会 的线性做法
洛谷题面传送门 一个线性做法. \(n\log n\) 解法可以戳这里查看 首先回顾一下 \(n\log n\) 解法的过程:我们对于每一个数 \(x\),考察其出现位置,设为 \(t_1,t_2,t ...
- P4062 [Code+#1]Yazid 的新生舞会
思路:分治 提交:2次 错因:数组开小 题解: 我们枚举一下众数\(x\). 设\(s[n]=\sum_{i=1}^n [a[i]==x]\) 那么对于区间\((l,r]\),有\(s[r]-s[l] ...
- [题解] [Code+#1]Yazid 的新生舞会
题面 题解 upd : \(cnt_i\) 代表值为 \(i\) 的个数 我们可以暴力枚举众数 \(k\) 把等于 \(k\) 的赋值成 1 , 不等于 \(k\) 的赋值成 -1 这样原序列就变成了 ...
- 【BZOJ5110】[CodePlus2017]Yazid 的新生舞会 线段树
[BZOJ5110][CodePlus2017]Yazid 的新生舞会 Description Yazid有一个长度为n的序列A,下标从1至n.显然地,这个序列共有n(n+1)/2个子区间.对于任意一 ...
- BZOJ.5110.[CodePlus2017]Yazid 的新生舞会(线段树/树状数组/分治)
LOJ BZOJ 洛谷 又来发良心题解啦 \(Description\) 给定一个序列\(A_i\).求有多少个子区间,满足该区间众数出现次数大于区间长度的一半. \(n\leq5\times10^5 ...
- 「CodePlus 2017 11 月赛」Yazid 的新生舞会(树状数组/线段树)
学习了新姿势..(一直看不懂大爷的代码卡了好久T T 首先数字范围那么小可以考虑枚举众数来计算答案,设当前枚举到$x$,$s_i$为前$i$个数中$x$的出现次数,则满足$2*s_r-r > 2 ...
- [loj 6253] Yazid的新生舞会
(很久之前刷的题现在看起来十分陌生a) 题意: 给你一个长度为n的序列A,定义一个区间$[l,r]$是“新生舞会的”当且仅当该区间的众数次数严格大于$\frac{r-l+1}{2}$,求有多少子区间是 ...
随机推荐
- Rancher 部署 loonflow 工单系统
上篇文章介绍用实例主机部署:https://www.cnblogs.com/weavepub/p/11672305.html,本文采用Rancher上部署. 文章所有的文件都托管在Github:htt ...
- Jenkins集成Sonar Quabe和权限配置
目录 安装Sonar Jenkins配置sonar Maven Jenkins Job配置 Pipeline Jenkins Job配置 Sonar权限管理 Sonar quality Gate通过阈 ...
- 集合类源码(六)Map(HashMap, Hashtable, LinkedHashMap, WeakHashMap)
HashMap 内部结构 内部是一个Node数组,每个Node都是链表的头,当链表的大小达到8之后链表转变成红黑树. put操作 final V putVal(int hash, K key, V v ...
- struts2的使用入门
虽然说Struts2现在已经被SpringMVC框架淘汰了,据说是有很多安全漏洞.但是Struts2作为一个成熟的MVC框架,还是有必要了解一下的,好歹是曾经风光一时的前辈,老祖宗的东西不能丢下,里面 ...
- 用 ubuntu 自带的 gome-screenshot 来实现类似QQ截图那样的功能,同时设置键盘快捷键
在window下习惯了使用ctrl+Alt+A截图,在linux还真有点不习惯,所以下面介绍一下替代的用法. 打开 ubuntu 的系统设置-->键盘-->快捷键:界面如下: 01 添加一 ...
- 你知道Object中有哪些方法及其作用吗?
一.引言二.Object方法详解1.1.registerNatives()1.2.getClass()1.2.1.反射三种方式:1.3.hashCode()1.4.equals()1.4.clone( ...
- Mysql系列(五)—— 分页查询及问题优化
一.用法 在Mysql中分页查询使用关键字limit.limit的语法如下: SELECT * FROM tbl LIMIT 5,10; # Retrieve rows 6-15 limit关键字带有 ...
- golang--生成某区间的随机数
import ( "crypto/rand" "fmt" "math" "math/big" ) func main() ...
- Kafka学习笔记之K8S内filebeat传输到kafka报错带解决方案
0x00 概述 filebeat非常轻量级,正常情况下占用的资源几乎都能忽略不计,但是部署后发现资源占用很大,所以怀疑是filebeat本身出了问题. 第一时间查看filebeat日志(默认路径/va ...
- 2019-11-29-WPF-高速书写-StylusPlugIn-原理
原文:2019-11-29-WPF-高速书写-StylusPlugIn-原理 title author date CreateTime categories WPF 高速书写 StylusPlugIn ...