【luogu P3901 数列找不同】 题解

对于区间查询的问题,提供一种思路:

莫队。

莫队是处理区间问题的乱搞神器,尤其是对于离线查询问题,当然也可以做在线查询,比如带修莫队。

对于有的题,莫队是乱搞骗分,而在某些地方,莫队是正解。

这道题来说,可以当做是萌新初学莫队的一个板子,而且莫队也好理
解。线段树树状数组这类也可以做,但是相比莫队而言麻烦些。(个
人见解,不喜勿喷。谢谢)


1st.关于莫队的思想:

先明白一点,莫队可以理解成:

优雅的暴力。

暴力算法几乎人人都会,所以莫队理解起来好理解。

如果让你暴力的话呢?

我们用一个cnt[ ]数组记录每种元素,用桶排的思想,枚举区间,每遇到一个元素对应的桶++,然后暴力一遍所有的桶,等于1的我们ans就++,这样统计不同的个数,看看是不是等于L到R,然后再清空桶和ans,做下一组询问。

对于一般的暴力算法往往会TLE,那么莫队是怎么做的呢?

首先:考虑我们有两个指针。一个叫做curL,另一个叫curR。他们对应的是所指的数的标号。这样我们可以利用这两个指针进行移动,每次只能向左或向右移动一步。移动的复杂度是O(1)。

eg:

我们现在处理了curL—curR区间内的数据,现在左右移动,比如curL到curL-1,只需要更新上一个新的3,即curL-1。

那么curL到curL+1,我们只需要去除掉当前curL的值。因为curL+1是已经维护好了的。

curR同理,但是要注意方向哦!curR到curR+1是更新,curR到cur-1是去除。

我们先计算一个区间 [curL curR] 的answer,这样的话,我们就可以用O(1)转移到 [curL-1 curR] [curL+1 curR] [curL curR+1] [curL curR-1] 上来并且求出这些区间的answer。

我们利用curL和curR,就可以移动到我们所需要求的[L R]上啦~

这样做会快很多,但是......

如果有个**数据,让你在每个L和R间来回跑,而且跨度很大呢??

我们每次只动一步,岂不是又T了??

但是这其实就是莫队算法的核心了。我们的莫队算法还有优化。

这就是莫队算法最精明的地方(我认为的qwq)

我们想,因为每次查询是离线的,所以我们先给每次的查询排一个序。

排序的方法是

分块。

我们把所有的元素分成多个块(即分块)。分了块跑的会更快。再按照右端点从小到大,左端点块编号相同按右端点从小到大。

为什么要这么排序呢?

如果不是按照分块排序,那么一种直观的办法是按照左端点排序,再按照右端点排序。但是这样的表现不好。特别是面对精心设计的数据,这样方法表现得很差。

举个栗子,有6个询问如下:(1, 100), (2, 2), (3, 99), (4, 4), (5, 102), (6, 7)。

这个数据已经按照左端点排序了。用上述方法处理时,左端点会移动6次,右端点会移动移动98+97+95+98+95=483次。

其实我们稍微改变一下询问处理的顺序就能做得更好:(2, 2), (4, 4), (6, 7), (5, 102), (3, 99), (1, 100)。

左端点移动次数为2+2+1+2+2=9次,比原来稍多。右端点移动次数为2+3+95+3+1=104,右端点的移动次数大大降低了。

上面的过程启发我们:我们不应该严格按照升序排序,而是根据需要灵活一点的排序方法

#### 这样,不断地移动端点指针+精妙的排序,就是普通莫队的思想啦~

2ec.Code

1.对于每组查询的记录和排序:

l,r为左右区间编号,p是第几组查询的编号(记录下来为了排序后不打乱顺序还按照原查询的顺序输出),bl是分块数。

struct query{
    int l, r, p;
}e[maxn];

bool cmp(query a, query b)
{
    return (a.l/bl) == (b.l/bl) ? a.r < b.r : a.l < b.l;
}

2.处理和初始变量:

answer就是所求答案, bl是分块数量, a[]是原序列, ans[]是记录原查询序列下的答案, cnt[]是记录对于每个数i, cnt[i]表示i出现过的次数, curL和curR不再解释, nm看题意要求。 add和del每个题有不同的写法。

int answer, a[maxn], m, n, bl, ans[maxn], cnt[maxn], k, curL = 1, curR = 0;
void add(int pos)//添加
{
    //do sth...
}
void del(int pos)//去除
{
    //do sth...
}
//一般写法都是边处理 边根据处理求答案。cnt[a[pos]]就是在pos位置上原序列a出现的次数。

3.主体部分及输出:

预处理查询编号,用四个while移动指针顺便处理。

在这里着重说下四个while

我们设想有一条数轴:

当curL < L 时,我们当前curL是已经处理好的了。所以del时先去除当前curL再++

当curL > L 时,我们当前curL是已经处理好的了。所以 add 时先 -- 再加上改后的curL

当curR > R 时,我们当前curR是已经处理好的了。所以del时先去除当前curR再 --

当curR < R 时,我们当前curR是已经处理好的了。所以 add 时先++再加上改后的curR

n = read(); m = read(); k = read();
    bl = sqrt(n);

    for(int i = 1; i <= n; i++)
    a[i] = read();

    for(int i = 1; i <= m; i++)
    {
        e[i].l = read(); e[i].r = read();
        e[i].p = i;
    }

    sort(e+1,e+1+m,cmp);

    for(int i = 1; i <= m; i++)
    {
        int L = e[i].l, R = e[i].r;
        while(curL < L)
        del(curL++);
        while(curL > L)
        add(--curL);
        while(curR > R)
        del(curR--);
        while(curR < R)
        add(++curR);
        ans[e[i].p] = answer;
    }
    for(int i = 1; i <= m; i++)
    printf("%d\n",ans[i]);
    return 0;

3th.关于这题题解

我的想法还是蛮暴力的,对于出现过的数直接记录下来,answer记录出现了多少个不同的数,如果answer等于现在的R-L+1,那么说明出现的数与L到R相同。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#define ri register
using namespace std;
const int maxn = 100010;
inline int read()
{
    int k=0;
    char c;
    c=getchar();
    while(!isdigit(c))c=getchar();
    while(isdigit(c)){k=(k<<3)+(k<<1)+c-'0';c=getchar();}
    return k;
}
int n, m, bl, answer = 0, curL, curR, cnt[maxn], a[maxn];
bool ans[maxn];
struct Query{
    int l, r, p;
}q[maxn];
bool cmp(const Query &a, const Query &b)
{
    return (a.l/bl) == (b.l/bl) ? a.r < b.r : a.l < b.l;
}
void add(int pos)
{
    if((++cnt[a[pos]]) == 1) ++answer;
}
void del(int pos)
{
    if((--cnt[a[pos]]) == 0) --answer;
}
int main()
{
    n = read();
    m = read();
    bl = sqrt(n);
    for(ri int i = 1; i <= n; i++)
    a[i] = read();

    for(ri int i = 1; i <= m; i++)
    {
        q[i].l = read();
        q[i].r = read();
        q[i].p = i;
    }

    sort(q+1,q+1+m,cmp);

    for(ri int i = 1; i <= m; i++)
    {
        int L = q[i].l, R = q[i].r;
        while(curL < L) del(curL++);
        while(curL > L) add(--curL);
        while(curR < R) add(++curR);
        while(curR > R) del(curR--);
        if(answer == (R-L+1))
        ans[q[i].p] = 1;
    }
    for(ri int i = 1; i <= m; i++)
    {
        if(ans[i] == 1)
        printf("Yes\n");
        else
        printf("No\n");
    }
    return 0;
}


如果对于莫队算法有什么疑问或者对我的题解有什么建议欢迎和我讨论,本人会尽力解答和虚心接受意见的。

原文地址:https://www.cnblogs.com/MisakaAzusa/p/9061431.html

时间: 2024-08-08 00:49:07

【luogu P3901 数列找不同】 题解的相关文章

【刷题】洛谷 P3901 数列找不同

题目描述 现有数列 \(A_1,A_2,\cdots,A_N\) ,Q 个询问 \((L_i,R_i)\) , \(A_{Li} ,A_{Li+1},\cdots,A_{Ri}\) 是否互不相同 输入输出格式 输入格式: 第1 行,2 个整数 \(N,Q\) 第2 行,N 个整数 \(A_{Li} ,A_{Li+1},\cdots,A_{Ri}\)? Q 行,每行2 个整数 \(L_i,R_i\)? 输出格式: 对每个询问输出一行,"Yes" 或者"No" 输入输出

P3901 数列找不同

莫队 #include<iostream> #include<cmath> #include<algorithm> #include<cstdio> using namespace std; const int N=100006; int n,m,l,r,nu,q; int ar[N],num[N],ans[N]; struct node{ int x,y,z; }a[N]; int get(){ char zy=getchar(); int z=1,y=0

Luogu P3412 仓鼠找$sugar$ $II$

Luogu P3412 仓鼠找\(sugar\) \(II\) 题目大意: 给定一棵\(n\)个点的树, 仓鼠每次移动都会等概率选择一个与当前点相邻的点,并移动到此点. 现在随机生成一个起点.一个终点(可能相同). 仓鼠希望知道它从起点走到终点的期望步数是多少. 数据范围: 对于\(30\%\),\(n\leq 5\) 对于\(60\%\),\(n\leq 5\times 10^3\) 对于\(100\%\),\(n \leq 7\times 10^6\) 请将输出答案(一个分数)模上\(998

Luogu P1062 数列

Luogu P1062 数列 题目说: 把所有\(k\)的方幂及所有有限个互不相等的\(k\)的方幂之和构成一个递增的序列. 这就是说,每一个\(k\)的方幂只能有或没有. 有为\(0\),没有为\(1\). 所以这些数在二进制下是这样的: \(1,10,11,100,101,110,111,1000\cdots\) 所以,第\(n\)项在十进制里就是\(n\). #include<bits/stdc++.h> using namespace std; int k,n,len; int num

P3901 【数列找不同】

这个题我们可以使用树状数组做 啥? 树状数组? 那个不是维护前缀和的东西吗? 各位看官,让我慢慢道来. 首先我们可以想到,对于一个询问$ [l,r] \(,只有\)[1,r]$中的数可能对这个询问有影响. 这就启示我们可以按照询问的右端点进行一波升序排序. 不过这和树状数组有什么关系呢? 对于同一个数\(x\),假设他在\([1,r]\) 出现了若干次. 对于我们的询问\([l,r]\)来说,只有最靠近\(r\)的\(x\)才最有可能影响到右端点为\(r\)的询问. 所以,我们就只用记录最靠近\

【luogu P1939 【模板】矩阵加速(数列)】 题解

题目链接:https://www.luogu.org/problemnew/show/P1939 对于矩阵推序列的式子: 由题意知: f[x+1] =1f[x] + 0f[x-1] + 1f[x-2] f[x] = 1f[x] + 0f[x-1] + 0f[x-2] f[x-1] = 0f[x] + 1f[x-1] + 0*f[x-2] 所以矩阵初项的系数: 1 1 0 0 0 1 1 0 0 #include <cstdio> #include <cstring> #includ

luogu P3398 仓鼠找sugar

题目描述 小仓鼠的和他的基(mei)友(zi)sugar住在地下洞穴中,每个节点的编号为1~n.地下洞穴是一个树形结构.这一天小仓鼠打算从从他的卧室(a)到餐厅(b),而他的基友同时要从他的卧室(c)到图书馆(d).他们都会走最短路径.现在小仓鼠希望知道,有没有可能在某个地方,可以碰到他的基友? 小仓鼠那么弱,还要天天被zzq大爷虐,请你快来救救他吧! 输入输出格式 输入格式: 第一行两个正整数n和q,表示这棵树节点的个数和询问的个数. 接下来n-1行,每行两个正整数u和v,表示节点u到节点v之

特征根法求通项+广义Fibonacci数列找循环节 - HDU 5451 Best Solver

Best Solver Problem's Link Mean: 给出x和M,求:(5+2√6)^(1+2x)的值.x<2^32,M<=46337. analyse: 这题需要用到高中的数学知识点:特征根法求递推数列通项公式. 方法是这样的: 对于这题的解法: 记λ1=5+2√6,λ2=5-2√6,则λ1λ2=1,λ1+λ2=10 根据韦达定理可以推导出:λ1,λ2的特征方程为 x^2-10x+1=0 再使用该特征方程反向推导出递推公式为:a[n]=10*a[n-1]-a[n-2] 再由特征根

CodeForces 450B Jzzhu and Sequences 费波纳茨数列+找规律+负数MOD

题目:Click here 题意:给定数列满足求f(n)mod(1e9+7). 分析:规律题,找规律,特别注意负数取mod. 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <cmath> 6 using namespace std; 7 const int M = 1e5+5; 8 const int