莫队&&分块
今天兔哥讲了一波莫队,比较有趣,先加一个链接,这是她的教程
这里就不详细说了,其实就是两个指针来优化的暴力。一开始排序函数有问题,没用上莫队的核心思想:把查询区间先排序,第一关键字是左指针所在的区间(注意,不是大小),第二关键字是右指针的大小。
然后一点点模拟就行了,左指针向前就减,否则加。
这里有一道板子题
题目描述 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; }
但是莫队的修改好像复杂度不是很优秀,而且不能在线只能离线处理,所以我又学了一个其他的结构:分块
再附上一个链接,讲的超级好:
其实这种东西和线段树区别不大,但是线段树好像复杂度更好?
然后去钢了一道黑题,做的怀疑人生,最后抄代码过的
题目背景 亲爱的哥哥: 你在那个城市里面过得好吗? 我在家里面最近很开心呢。昨天晚上奶奶给我讲了那个叫「绝望」的大坏蛋的故事的说!它把人们的房子和田地搞坏,还有好多小朋友也被它杀掉了。我觉得把那么可怕的怪物召唤出来的那个坏蛋也很坏呢。不过奶奶说他是很难受的时候才做出这样的事的…… 最近村子里长出了一大片一大片的蒲公英。一刮风,这些蒲公英就能飘到好远的地方了呢。我觉得要是它们能飘到那个城市里面,让哥哥看看就好了呢! 哥哥你要快点回来哦! 爱你的妹妹 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; }
大家加油!!!
只想找一个不会伤害我的人