【洛谷4396/BZOJ3236】[AHOI2013]作业(莫队+分块/树状数组/线段树)
题目:
这题似乎BZOJ上数据强一些?
分析:
这题真的是……一言难尽
发现题面里没说权值的范围,怕出锅就写了离散化。后来经过面向数据编程(以及膜神犇代码)知道最大权值\(1e5\)(下文用\(M\)表示最大权值。注意如果没有这个限制,把所有数的权值和询问中提到的权值一起离散化后\(M\)也可以达到\(n+2m=2.1e6\)),如果没这个限制我很想知道正解应该怎么写……下面再细说
首先看到这种询问某个区间内在某个区域内值的数量的题,显然能想到把询问离线下来,用个什么数据结构维护莫队。
然后我大概脑补了一下,这个“数据结构”似乎可以用权值线段树?每个区间维护\(sum\)和\(num\)对应两种询问。对于叶子节点,若\(sum\)为\(0\)则\(num\)为\(0\),否则\(num\)为\(1\)。对于非叶子节点,\(sum\)和\(num\)都是分别是两儿子的\(sum\)/\(num\)之和。这样做正确性是显然的,复杂度\(O(m\sqrt n \times log_2M)\),大概是\(1e6\times \sqrt{1e5}\times 21\approx 6.64e9\),过个鬼哦。
树状数组做法类似。维护两个树状数组\(sum\)和\(num\),同样对应两个询问。给\(a\)加的时候如果原本\(sum[a]\)是\(0\)就给\(num[a]\),给\(a\)减的时候如果原本\(sum[a]\)是\(1\)就给\(num[a]\)减。复杂度和线段树是一样的,所以也过不去。
然后没想到竟然分块能过……把权值分块维护,\(blsum\)和\(blnum\)表示块内两种询问的答案,\(num[a]\)表示\(a\)的出现次数,维护和查询方式详见代码。
在我僵化的思维里\(log_2n\)的树状数组和线段树比\(\sqrt n\)的分块要优,但是这道题里分块的修改是\(O(1)\),查询是\(O(\sqrt M)\)。而查询不需要乘上莫队的\(O(\sqrt n)\),所以最终复杂度是\(O(m (\sqrt n+\sqrt M))\),大概是\(1e6\times(\sqrt{1e5} +\sqrt{2.1e6})=1.77e9\),还是悬。如果权值最大是\(1e5\)还是能跑过去的。
代码:
(线段树和树状数组没有AC)
线段树:\(161\)行,本地\(83\)s,内存\(100.4\)MB,BZOJ上TLE。下方代码中省略部分和树状数组基本一致
namespace Segment_Tree
{
struct node
{
int sum, num;
}tree[(N + (M << 1)) << 2];
pii operator + (const pii &a, const pii &b)
{
return make_pair(a.first + b.first, a.second + b.second);
}
pii operator += (pii &a, const pii &b)
{
return a = a + b;
}
void update(const int rot)
{
tree[rot].sum = tree[rot << 1].sum + tree[rot << 1 | 1].sum;
tree[rot].num = tree[rot << 1].num + tree[rot << 1 | 1].num;
}
void change(const int rot, const int lt, const int rt, const int pos, const int x)
{
if (lt == rt)
{
tree[rot].sum += x;
tree[rot].num = (tree[rot].sum ? 1 : 0);
return;
}
int mid = (lt + rt) >> 1;
if (pos <= mid)
change(rot << 1, lt, mid, pos, x);
else
change(rot << 1 | 1, mid + 1, rt, pos, x);
update(rot);
}
pii query(const int rot, const int lt, const int rt, const int ls, const int rs)
{
if (ls <= lt && rt <= rs)
return make_pair(tree[rot].sum, tree[rot].num);
int mid = (lt + rt) >> 1;
pii ans = make_pair(0, 0);
if (ls <= mid)
ans += query(rot << 1, lt, mid, ls, rs);
if (rs > mid)
ans += query(rot << 1 | 1, mid + 1, rt, ls, rs);
return ans;
}
}
树状数组:\(153\)行,本地\(23\)s(一样的复杂度比线段树快近\(3\)倍,结果BZOJ上还是TLE什么鬼),内存\(37.0\)MB
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
namespace zyt
{
template<typename T>
inline void read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != '-' && !isdigit(c));
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
inline void write(const pair<int, int> &a)
{
write(a.first), putchar(' '), write(a.second);
}
const int N = 1e5 + 10, M = 1e6 + 10;
int n, m, arr[N], belong[N];
typedef pair<int, int> pii;
pii ans[M];
struct _ask
{
int l, r, a, b, id;
bool operator < (const _ask &b) const
{
return belong[l] == belong[b.l] ? r < b.r : l < b.l;
}
}ask[M];
int cnt, tmp[N + (M << 1)];
class Tree_Array
{
private:
int data[N];
inline int lowbit(const int x)
{
return x & (-x);
}
public:
inline void add(int a, const int x)
{
while (a <= cnt)
data[a] += x, a += lowbit(a);
}
inline int query(int a)
{
int ans = 0;
while (a)
ans += data[a], a -= lowbit(a);
return ans;
}
inline int query(const int l, const int r)
{
return query(r) - query(l - 1);
}
}sum, num;
void update(const int pos, const int x)
{
if (x == 1)
{
if (!sum.query(pos, pos))
num.add(pos, 1);
sum.add(pos, 1);
}
if (x == -1)
{
sum.add(pos, -1);
if (!sum.query(pos, pos))
num.add(pos, -1);
}
}
void Mo_Algorithm()
{
int l = 1, r = 1;
update(arr[1], 1);
for (int i = 1; i <= m; i++)
{
while (l < ask[i].l)
update(arr[l++], -1);
while (l > ask[i].l)
update(arr[--l], 1);
while (r < ask[i].r)
update(arr[++r], 1);
while (r > ask[i].r)
update(arr[r--], -1);
if (ask[i].a > ask[i].b)
ans[ask[i].id] = make_pair(0, 0);
else
ans[ask[i].id] = make_pair(sum.query(ask[i].a, ask[i].b), num.query(ask[i].a, ask[i].b));
}
}
int work()
{
int block;
read(n), read(m);
block = sqrt(n);
for (int i = 1; i <= n; i++)
{
read(arr[i]), tmp[cnt++] = arr[i];
belong[i] = (i - 1) / block + 1;
}
for (int i = 1; i <= m; i++)
{
read(ask[i].l), read(ask[i].r), read(ask[i].a), read(ask[i].b);
ask[i].id = i;
tmp[cnt++] = ask[i].a, tmp[cnt++] = ask[i].b;
}
sort(tmp, tmp + cnt);
cnt = unique(tmp, tmp + cnt) - tmp;
for (int i = 1; i <= n; i++)
arr[i] = upper_bound(tmp, tmp + cnt, arr[i]) - tmp;
for (int i = 1; i <= m; i++)
{
ask[i].a = upper_bound(tmp, tmp + cnt, ask[i].a) - tmp;
ask[i].b = upper_bound(tmp, tmp + cnt, ask[i].b) - tmp;
}
sort(ask + 1, ask + m + 1);
Mo_Algorithm();
for (int i = 1; i <= m; i++)
write(ans[i]), putchar('\n');
return 0;
}
}
int main()
{
return zyt::work();
}
分块:\(136\)行,本地\(8\)s,内存\(28.5\)MB,BZOJ上AC。这份代码是我知道最大权值\(1e5\)后写的,所以没有离散化。
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
namespace zyt
{
template<typename T>
inline void read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != '-' && !isdigit(c));
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
inline void write(const pair<int, int> &a)
{
write(a.first), putchar(' '), write(a.second);
}
const int N = 1e5 + 10, M = 1e6 + 10, BL = 510;
int n, m, arr[N], belong[N];
typedef pair<int, int> pii;
pii ans[M];
struct _ask
{
int l, r, a, b, id;
bool operator < (const _ask &b) const
{
return belong[l] == belong[b.l] ? r < b.r : l < b.l;
}
}ask[M];
int blsum[BL], blnum[BL], num[N], begin[BL];
inline void update(const int pos, const int x)
{
blsum[belong[pos]] += x;
if (x == -1 && !--num[pos])
--blnum[belong[pos]];
if (x == 1 && !num[pos]++)
++blnum[belong[pos]];
}
inline pii query(const int l, const int r)
{
pii ans = make_pair(0, 0);
if (belong[r] == belong[l])
{
for (int i = l; i <= r; i++)
if (num[i])
ans.first += num[i], ++ans.second;
}
else
{
for (int i = belong[l] + 1; i < belong[r]; i++)
ans.first += blsum[i], ans.second += blnum[i];
for (int i = l; i < begin[belong[l] + 1]; i++)
if (num[i])
ans.first += num[i], ++ans.second;
for (int i = begin[belong[r]]; i <= r; i++)
if (num[i])
ans.first += num[i], ++ans.second;
}
return ans;
}
void Mo_Algorithm()
{
int l = 1, r = 1, block = sqrt(N), blnum = ceil(N / (double)block);
for (int i = 1; i < N; i++)
belong[i] = (i - 1) / block + 1;
for (int i = 1; i <= blnum; i++)
begin[i] = (i - 1) * block + 1;
update(arr[1], 1);
for (int i = 1; i <= m; i++)
{
while (l < ask[i].l)
update(arr[l++], -1);
while (l > ask[i].l)
update(arr[--l], 1);
while (r < ask[i].r)
update(arr[++r], 1);
while (r > ask[i].r)
update(arr[r--], -1);
if (ask[i].a > ask[i].b)
ans[ask[i].id] = make_pair(0, 0);
else
ans[ask[i].id] = query(ask[i].a, ask[i].b);
}
}
int work()
{
int block;
read(n), read(m);
block = sqrt(n);
for (int i = 1; i <= n; i++)
{
read(arr[i]);
belong[i] = (i - 1) / block + 1;
}
for (int i = 1; i <= m; i++)
{
read(ask[i].l), read(ask[i].r), read(ask[i].a), read(ask[i].b);
ask[i].id = i;
}
sort(ask + 1, ask + m + 1);
Mo_Algorithm();
for (int i = 1; i <= m; i++)
write(ans[i]), putchar('\n');
return 0;
}
}
int main()
{
return zyt::work();
}
【洛谷4396/BZOJ3236】[AHOI2013]作业(莫队+分块/树状数组/线段树)的更多相关文章
- BZOJ3236:[AHOI2013]作业(莫队,分块)
Description Input Output Sample Input 3 4 1 2 2 1 2 1 3 1 2 1 1 1 3 1 3 2 3 2 3 Sample Output 2 2 1 ...
- bzoj3809 Gty的二逼妹子序列 & bzoj3236 [Ahoi2013]作业 莫队+分块
题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=3809 https://lydsy.com/JudgeOnline/problem.php?id ...
- [AHOI2013]作业 (莫队+分块)
[AHOI2013]作业 (莫队+分块) 题面 给定了一个长度为n的数列和若干个询问,每个询问是关于数列的区间[l,r],首先你要统计该区间内大于等于a,小于等于b的数的个数,其次是所有大于等于a,小 ...
- bzoj 3236: 洛谷 P4396: [AHOI2013]作业 (莫队, 分块)
题目传送门:洛谷P4396. 题意简述: 给定一个长度为\(n\)的数列.有\(m\)次询问,每次询问区间\([l,r]\)中数值在\([a,b]\)之间的数的个数,和数值在\([a,b]\)之间的不 ...
- BZOJ3236[Ahoi2013]作业——莫队+树状数组/莫队+分块
题目描述 输入 输出 样例输入 3 4 1 2 2 1 2 1 3 1 2 1 1 1 3 1 3 2 3 2 3 样例输出 2 2 1 1 3 2 2 1 提示 N=100000,M=1000000 ...
- Bzoj 3236: [Ahoi2013]作业 莫队,分块
3236: [Ahoi2013]作业 Time Limit: 100 Sec Memory Limit: 512 MBSubmit: 1113 Solved: 428[Submit][Status ...
- [洛谷P1198/BZOJ1012][JSOI2008] 最大数 - 树状数组/线段树?
其实已经学了树状数组和线段树,然而懒得做题,所以至今没写多少博客 Description 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作. 语法:Q L 功能:查询当前数列中末尾L个数 ...
- 洛谷P2414 阿狸的打字机 [NOI2011] AC自动机+树状数组/线段树
正解:AC自动机+树状数组/线段树 解题报告: 传送门! 这道题,首先想到暴力思路还是不难的,首先看到y有那么多个,菜鸡如我还不怎么会可持久化之类的,那就直接排个序什么的然后按顺序做就好,这样听说有7 ...
- 洛谷P2880 [USACO07JAN] Balanced Lineup G(树状数组/线段树)
维护区间最值的模板题. 1.树状数组 1 #include<bits/stdc++.h> 2 //树状数组做法 3 using namespace std; 4 const int N=5 ...
随机推荐
- Adobe AIR 代码签名证书使用指南
Symantec,Thawte,GlobalSign 签发的代码签名证书都可以签名AIR文件.如果您还没有代码签名证书,请联系易维信(EVTrust)购买Adobe AIR 代码签名证书. 1.签名工 ...
- java中List遍历删除元素-----不能直接 list.remove()
https://blog.csdn.net/github_2011/article/details/54927531 这是List接口中的方法,List集合调用此方法可以得到一个迭代器对象(Itera ...
- Mysql中Group By使用Having语句配合查询(where和having区别)
注意 : having语句一般结合GROUP BY一起使用的..... Having短语与WHERE的区别!!! WHERE子句作用于基表或视图,从中选择满足条件的元组.HAVING短语作用于组,从中 ...
- [codevs 1183][泥泞的道路(二分+spfa)
题目:http://dev.codevs.cn/problem/1183/ 分析:这个和最优比率生成树很像,都可以二分答案的,只不过判定方面一个是求是否有最短路径,一个是求是否有生成树.假设等待判定的 ...
- Cisco IOU Web Interface : Web IOU
https://github.com/dainok http://sns.clnchina.com.cn/space.php?uid=404779&do=blog&id=4298 ht ...
- 链表中倒数第N个元素——剑指Offer
https://www.nowcoder.net/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage= ...
- C/C++异常处理机制
1.C语言异常处理 1.1 异常终止 标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于<stdlib.h>头文件中.这两个函数本身不能检测 ...
- 每日五题(jsp)
1.forward 和 redirect 的差别 答: 1.从地址栏显示来说 forward是server请求资源,server直接訪问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容 ...
- 用VBS控制鼠标(获取鼠标坐标、鼠标移动、鼠标单击、鼠标双击、鼠标右击)
Demon's Blog 忘记了,喜欢一个人的感觉 Demon's Blog » 程序设计 » 用VBS控制鼠标(获取鼠标坐标.鼠标移动.鼠标单击.鼠标双击.鼠标右击) « bbPress积分 ...
- iOS UITextView 高度随文字自己主动添加,并尾随键盘移动(二)
上节地址:http://blog.csdn.net/lwjok2007/article/details/47401293 接着上节我们来实现 输入框自己主动调节高度 首先,我们得知道,要推断是否该换行 ...