博客园 首页 私信博主 显示目录 隐藏目录 管理

洛谷 题解 SP3267 【DQUERY - D-query】

今天机房讲了莫队。

但是蒟蒻我并没有听懂,所以晚上回家恶补,才弄明白莫队。

莫队是莫涛大神发明的,它的作用就是用优秀的复杂度求解于一些区间之间的操作,莫队其实就是一个优雅的暴力,它的复杂度是O(n sqrt(n));

以此为例,其实这题和这题是一样的,不过P1972会卡莫队。

回到此题:

题意很直白,我就不多赘述;

思路1:

暴力,这题最朴素的作法无非就是从l到r扫一遍,用数组记下出现的数,然后求值:

#include<bits/stdc++.h>
using namespace std;
#define maxn 100010
bool flag[maxn];
int n, m, a[maxn], sum, ans, l, r, x, y;
int main()
{
//freopen("count.in","r",stdin);
//freopen("count.out","w",stdout);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++ i)
scanf("%d", &a[i]);
for(int i = 1; i <= m; ++ i)
{
scanf("%d%d%d%d", &l, &r, &x, &y);
memset(flag, 0, sizeof(flag));
sum = ans = 0;
for(int j = l; j <= r; ++ j)
if(a[j] >= x && a[j] <= y)
{
++ sum;
if(flag[a[j]] == 0)
{
flag[a[j]] = 1;
++ ans;
}
}
printf("%d %d\n", sum, ans);
}
return 0;
}

ans即为所求;

但是这种作法的复杂度是O(n*n)的,肯定过不了;

那么就需要一些优化:

我们用两个指针不断的移动到相应的区间;

int l = 0, r = 1;

然后在移动的时候,我们就不停的add,del;

如果要想右,就add;

否则,就del;

void add(int x)
{
if(cnt[a[x]] == 0)
++ now;
++ cnt[a[x]];
}
void del(int x)
{
-- cnt[a[x]];
if(cnt[a[x]] == 0)
-- now;
}
while(l < q[i].l)
del(l ++);
while(l > q[i].l)
add(-- l);
while(r < q[i].r)
add(++ r);
while(r > q[i].r)
del(r --);

就是上面这样,

这就是莫队?

不,还有一个地方,

我们来假设要查询的区间为[1, 10000000]呢?

那不一样还是没有优化。

所以莫队还要一个很重要的地方,就是排序;

该如何排序?

如果按左端点排序,那么右端点就会不好表示;

如果按右端点排序,那么左端点就会不好表示;

这个时候,分块大法万岁;

把长度为n的序列,分成sqrt(n)个块;

把查询区间按照左端点所在块的序号排个序,如果左端点所在块相同,再按右端点排序。

其实,蒟蒻我也不明白这为什么会快200多ms,但它就是会;

整个程序的复杂度为O(n * sqrt(n));

代码时间:

#include<bits/stdc++.h>
using namespace std;

#define maxn 1000010
int n, m, a[maxn], cnt[maxn], ans[maxn], bein[maxn], l = 1, r, now;

struct node
{
int l, r, id;
}q[maxn];

bool cmp(node a, node b)
{
return bein[a.l] == bein[b.l] ? a.r < b.r : bein[a.l] < bein[b.l];
}

void add(int x)//加入操作 (右移 
{
if(cnt[a[x]] == 0)
++ now;
++ cnt[a[x]];
}

void del(int x)//删除(左移 
{
-- cnt[a[x]];
if(cnt[a[x]] == 0)
-- now;
}

void print(int x)//要从后往前输出,所以来递归输出 
{
if(x / 10)
print(x / 10);
//printf("%d", x % 10);
putchar(x % 10 + '0');
//printf("K"); 
}

int main()
{
scanf("%d", &n);//输入 
for(int i = 1; i <= ceil((double) n / sqrt(n)); ++ i)
for(int j = (i - 1) * sqrt(n) + 1; j <= i * sqrt(n); ++ j)
bein[j] = i;//这是分的块 
for(int i = 1; i <= n; ++ i)
scanf("%d", &a[i]);//还是输入 
scanf("%d", &m);//继续输入 
for(int i = 1; i <= m; ++ i)
{
scanf("%d%d", &q[i].l, &q[i].r);//还要输入 
q[i].id = i;//记录下序号,cmp中要用 
}
sort(q + 1, q + m + 1, cmp);//排序 
/*这种作法就不需要add和del 
for(int i = 1; i <= m; ++i) {
int ql = q[i].l, qr = q[i].r;
while(l < ql) now -= !--cnt[aa[l++]];
while(l > ql) now += !cnt[aa[--l]]++;
while(r < qr) now += !cnt[aa[++r]]++;
while(r > qr) now -= !--cnt[aa[r--]];
ans[q[i].id] = now;
}
*/
for(int i = 1; i <= m; ++ i)
{
while(l < q[i].l)//l右移 
del(l ++);
while(l > q[i].l)//l左移 
add(-- l);
while(r < q[i].r)//r右移 
add(++ r);
while(r > q[i].r)//r左移 
del(r --);
ans[q[i].id] = now;
}
for(int i = 1; i <= m; ++ i)
{
print(ans[i]);//输出 
printf("\n");//记得换行 
}
return 0;
}

因为在本蒟蒻最开始学的时候没有懂,所以有拜读了WAMonster大佬的文章,可能在思路上会有部分相同,而且我也强力推荐这位大佬的博客,写的特别好。

posted @ 2019-12-12 11:50  Flash_plus  阅读(91)  评论(0编辑  收藏  举报