莫队&&分块

今天兔哥讲了一波莫队,比较有趣,先加一个链接,这是她的教程

rabbithu.cnblogs.com

这里就不详细说了,其实就是两个指针来优化的暴力。一开始排序函数有问题,没用上莫队的核心思想:把查询区间先排序,第一关键字是左指针所在的区间(注意,不是大小),第二关键字是右指针的大小。

然后一点点模拟就行了,左指针向前就减,否则加。

这里有一道板子题

题目描述

HH 有一串由各种漂亮的贝壳组成的项链。HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH 不断地收集新的贝壳,因此,他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?这个问题很难回答……因为项链实在是太长了。于是,他只好求助睿智的你,来解决这个问题。
输入输出格式
输入格式:

第一行:一个整数N,表示项链的长度。

第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为0 到1000000 之间的整数)。

第三行:一个整数M,表示HH 询问的个数。

接下来M 行:每行两个整数,L 和R(1 ≤ L ≤ R ≤ N),表示询问的区间。

输出格式:

M 行,每行一个整数,依次表示询问对应的答案。

直接贴代码(数据加强之后AC不了,但貌似所有的算法都AC不了?)

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
ll que[500010],n,m,bk;
struct node{
    int ans;
    int l,r;
};
node k[200010];
int pl = 1,pr = 0;
bool cmp(node a,node b)
{
    if(a.l / bk != b.l / bk)
    {
        return a.l < b.l;
    }
    else
    {
        return a.r < b.r;
    }
}
ll cnt[200010],num = 0;
void add(int a)
{
    if(!cnt[a])
    num++;
    cnt[a]++;
}
void del(int a)
{
    cnt[a]--;
    if(!cnt[a])
    num--;
}
ll ans[200010];
int main()
{
    cin>>n;
    bk = ceil(sqrt(n));
    for(int i = 1;i <= n;i++)
    {
        scanf("%lld",&que[i]);
    }
    cin>>m;
    for(int i = 1;i <= m;i++)
    {
        scanf("%d%d",&k[i].l,&k[i].r);
        k[i].ans = i;
    }
    sort(k + 1,k + m + 1,cmp);
    for(int i = 1;i <= m;i++)
    {
        while(pl < k[i].l)
        del(que[pl++]);
        while(pl > k[i].l)
        add(que[--pl]);
        while(pr > k[i].r)
        del(que[pr--]);
        while(pr < k[i].r)
        add(que[++pr]);
        ans[k[i].ans] = num;
    }
    for(int i = 1;i <= m;i++)
    {
        printf("%lld\n",ans[i]);
    }
    return 0;
}

还有一个题,是莫队的来源,好像莫队是队长从这一题的论文答辩发明的。

题目描述

作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……

具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。

你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。

然而数据中有L=R的情况,请特判这种情况,输出0/1。
输入输出格式
输入格式:

输入文件第一行包含两个正整数N和M。N为袜子的数量,M为小Z所提的询问的数量。接下来一行包含N个正整数Ci,其中Ci表示第i只袜子的颜色,相同的颜色用相同的数字表示。再接下来M行,每行两个正整数L,R表示一个询问。

输出格式:

包含M行,对于每个询问在一行中输出分数A/B表示从该询问的区间[L,R]中随机抽出两只袜子颜色相同的概率。若该概率为0则输出0/1,否则输出的A/B必须为最简分数。(详见样例)

输入输出样例
输入样例#1: 复制

6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6

输出样例#1: 复制

2/5
0/1
1/1
4/15

 这个是计数,但是本质上没什么区别,上代码

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
ll que[50010],bk,n,m;
struct node{
    int ans;
    int l,r;
};
node k[50010];
ll pl = 1,pr = 0;
ll ava[50010],bvb[50010];
bool cmp(node a,node b)
{
    if(a.l / bk == b.l / bk)
    {
        return a.r < b.r;
    }
    else
    {
        return a.l < b.l;
    }
}
ll gcd(ll a,ll b)
{
    ll p;
    while(a % b != 0)
    {
        p = a % b;
        a = b;
        b = p;
    }
    return b;
}
ll cnt[50010],num = 0;
void add(int a)
{
    num -= cnt[a] * cnt[a];
    cnt[a]++;
    num += cnt[a] * cnt[a];
}
void del(int a)
{
    num -= cnt[a] * cnt[a];
    cnt[a]--;
    num += cnt[a] * cnt[a];
}
ll ans[50010],aa,bb,cc;
int main()
{
    cin>>n>>m;
    for(int i = 1;i <= n;i++)
    {
        scanf("%lld",&que[i]);
    }
    bk = ceil(sqrt(n));
    for(int i = 1;i <= m;i++)
    {
        scanf("%d%d",&k[i].l,&k[i].r);
        k[i].ans = i;
    }
    sort(k + 1,k + m + 1,cmp);
    for(int i = 1;i <= m;i++)
    {
        if(k[i].l == k[i].r)
        {
            ava[k[i].ans] = 0;
            bvb[k[i].ans] = 1;
            continue;
        }
        while(pl < k[i].l)
        del(que[pl++]);
        while(pl > k[i].l)
        add(que[--pl]);
        while(pr > k[i].r)
        del(que[pr--]);
        while(pr < k[i].r)
        add(que[++pr]);
        pl = k[i].l;
        aa = num + k[i].l - k[i].r - 1;
        bb = (ll)(k[i].r - k[i].l + 1) * (k[i].r - k[i].l);
        cc = gcd(aa,bb);
        aa /= cc;bb /= cc;
        ava[k[i].ans] = aa;
        bvb[k[i].ans] = bb;
//        cout<<aa<<"/"<<cc<<endl;
    }
    for(int i = 1;i <= m;i++)
    {
        printf("%lld/%lld\n",ava[i],bvb[i]);
    }
    return 0;
}

但是莫队的修改好像复杂度不是很优秀,而且不能在线只能离线处理,所以我又学了一个其他的结构:分块

再附上一个链接,讲的超级好:

http://hzwer.com/8053.html

其实这种东西和线段树区别不大,但是线段树好像复杂度更好?

然后去钢了一道黑题,做的怀疑人生,最后抄代码过的

题目背景

亲爱的哥哥:

你在那个城市里面过得好吗?

我在家里面最近很开心呢。昨天晚上奶奶给我讲了那个叫「绝望」的大坏蛋的故事的说!它把人们的房子和田地搞坏,还有好多小朋友也被它杀掉了。我觉得把那么可怕的怪物召唤出来的那个坏蛋也很坏呢。不过奶奶说他是很难受的时候才做出这样的事的……

最近村子里长出了一大片一大片的蒲公英。一刮风,这些蒲公英就能飘到好远的地方了呢。我觉得要是它们能飘到那个城市里面,让哥哥看看就好了呢!

哥哥你要快点回来哦!

爱你的妹妹 Violet

Azure 读完这封信之后微笑了一下。

“蒲公英吗……”
题目描述

在乡下的小路旁种着许多蒲公英,而我们的问题正是与这些蒲公英有关。

为了简化起见,我们把所有的蒲公英看成一个长度为n的序列 (a1,a2..an)(a_1,a_2..a_n)(a1​,a2​..an​) ,其中 aia_iai​ 为一个正整数,表示第i棵蒲公英的种类编号。

而每次询问一个区间 [l,r],你需要回答区间里出现次数最多的是哪种蒲公英,如果有若干种蒲公英出现次数相同,则输出种类编号最小的那个。

注意,你的算法必须是在线的
输入输出格式
输入格式:

第一行两个整数 n,m ,表示有n株蒲公英,m 次询问。

接下来一行n个空格分隔的整数 aia_iai​ ,表示蒲公英的种类

再接下来m 行每行两个整数 l0,r0l_0,r_0l0​,r0​ ,我们令上次询问的结果为 x(如果这是第一次询问, 则 x=0)。

令 l=(l0+x−1)modn+1,r=(r0+x−1)modn+1l=(l_0+x-1)\bmod n + 1,r=(r_0+x-1) \bmod n + 1l=(l0​+x−1)modn+1,r=(r0​+x−1)modn+1 ,如果 l>r,则交换 l,r 。

最终的询问区间为[l,r]。

输出格式:

输出m 行。每行一个整数,表示每次询问的结果。

这个题的思路不算难,就是离散化+分块处理在线找众数,但是代码真是狗

#include<iostream>
#include<cstdio>
#include<map>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
int n,m,blo,id;
int v[500005],bl[500005];
int f[1005][1005];  //f[i][j]表示i~j的众数是多少 
map<int,int>mp;
int val[500005],cnt[500005];
vector<int>ve[500005];
void pre(int x)
{   
    memset(cnt,0,sizeof(cnt));
    int mx = 0,ans = 0;
    for(int i=(x - 1) * blo + 1;i <= n;i++)
    {       
        cnt[v[i]]++;
        int t = bl[i];
        if(cnt[v[i]] > mx || (cnt[v[i]] == mx && val[v[i]] < val[ans])) //找x~t真正的众数 
            ans = v[i],mx = cnt[v[i]];
        f[x][t] = ans;
    }
}
int query(int l,int r,int x)
{
    int t = upper_bound(ve[x].begin(),ve[x].end(),r) - lower_bound(ve[x].begin(),ve[x].end(),l);
    return t;
} 
int query(int a,int b)
{
    int ans,mx;
    ans = f[bl[a] + 1][bl[b] - 1];
    mx = query(a,b,ans);  //整区间里的众数 
    for(int i = 1;i <= min(blo * bl[a],b);i++)
    {
        int t = query(a,b,v[i]);
        if(t > mx || (t == mx && val[v[i]] < val[ans]))
        {
            ans = v[i];
            mx = t;
        }
    }
    if(bl[a] != bl[b])
    {
        for(int i = (bl[b] - 1) * blo + 1;i <= b;i++)
        {
            int t = query(a,b,v[i]);
            if(t > mx || (t == mx && val[v[i]] < val[ans]))
            ans = v[i],mx = t;
        }
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    blo = 200;
    int ans = 0;
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&v[i]);
        if(!mp[v[i]])
        {
            mp[v[i]] = ++id; //离散化 
            val[id] = v[i];    //第一次出现的位置
        }
        v[i] = mp[v[i]];
        ve[v[i]].push_back(i);
    }
    for(int i = 1;i <= n;i++)     //处理i在第几个块
    bl[i] = (i - 1) / blo + 1;
    for(int i = 1;i <= bl[n];i++)  //预处理f数组 
    pre(i);
    for(int i = 1;i <= m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        a = (a + ans - 1) % n + 1;
        b = (b + ans - 1) % n + 1;
        if(a > b)
        swap(a,b);
        ans = val[query(a,b)];
        printf("%d\n",ans);
    }
    return 0;
}

大家加油!!!

posted @ 2018-06-09 20:45  DukeLv  阅读(671)  评论(0编辑  收藏  举报