小Z的袜子 (HYSBZ-2038)
作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L?尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。 你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。
思路:模板题
某个区间\([l,r]\)的答案:
\[
{\sum_{i为出现的颜色} sum[i]\cdot (sum[i]-1)/2}\over {C_{r-l+1}^2}
\]
- 对询问排序
- \([l,r]\),以\(l\)所在块的编号为第一关键字,r为第二关键字从小到大排序。
- 暴力维护答案的分子部分即可
- 可以发现答案分子分母同时将2约掉,分子展开后变成\(sum[i]\cdot sum[i] - sum[i]\)可以发现对于所有的 i ,\(sum[i]\)的和将变成\(r-l+1\),所以我们只需要维护所有\(sum[i]\)的平方和即可
#include <iostream>
#include <algorithm>
#include <math.h>
#include <cstdio>
using namespace std;
const int N = 50010;
typedef long long ll;
int a[N],be[N],n,m;
ll res = 0,sum[N];
struct node{
int l,r,id;
ll A,B;
}q[N];
//如果在同一块,则按照右端点排序,否则按照左端点
bool cmp1(node a,node b){
return be[a.l] == be[b.l] ? a.r < b.r : a.l < b.l;
}
bool cmp(node a,node b){
return a.id < b.id;
}
ll gcd(ll a,ll b){return b == 0 ? a : gcd(b,a%b);}
ll S(ll x){return x * x;}
//先减去上一次的影响,修改后再重新加新的影响
void move(int pos,int add){res -= S(sum[a[pos]]);sum[a[pos]] += add;res += S(sum[a[pos]]);}
int main(){
scanf("%d%d",&n,&m);
int base = sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);q[i].id = i;
be[i] = (i-1) / base + 1;//be[i]即i在第几块
}
for(int i=1;i<=m;i++)scanf("%d%d",&q[i].l,&q[i].r);
sort(q+1,q+1+m,cmp1);
int l = 1,r = 0;
res = 0;//res为当点询问区间内出现的所有颜色的个数平方和
for(int i=1;i<=m;i++){
//暴力调整区间,维护res
while(l < q[i].l)move(l++,-1);
while(l > q[i].l)move(--l,1);
while(r < q[i].r)move(++r,1);
while(r > q[i].r)move(r--,-1);
if(l == r){
q[i].A = 0;q[i].B = 1;continue;
}
q[i].A = res - (r - l + 1);//计算答案分子部分
q[i].B = 1ll * (r - l + 1) * (r - l);//分母部分
ll g = gcd(q[i].A,q[i].B);//约分
q[i].A /= g;
q[i].B /= g;
}
sort(q+1,q+1+m,cmp);
for(int i=1;i<=m;i++){
printf("%lld/%lld\n",q[i].A,q[i].B);
}
return 0;
}
普通莫队优化
可以发现当第一块内的询问处理完之后,r的位置应该特别靠后,但是当移动到下一个块之后,r可能会往前移动很多,比如如下询问
//第一个块
1 50
2 100
//第二个块
12 13
14 100
在完成[2,100]的询问后,r从100-> 13 然后又从13 -> 100。这样显然不如100->100, 100 -> 13。
如何优化?
相邻两块之间r的排序规则相反即可
即奇数块按照升序,偶数快按照降序
Result | Memory | Time |
---|---|---|
Accepted | 3456 kb | 1840 ms |
Accepted | 3456 kb | 1392 ms |
下面的是优化过的。
bool cmp1(node a,node b){
return be[a.l] == be[b.l] ?
(be[a.l]&1 ? a.r < b.r : a.r > b.r)
: a.l < b.l;
}
带修改的莫队
考虑普通莫队加入修改修做,如果修改操作可以\(O(1)\)的应用以及撤销(同时也要维护当前区间的答案),那么可以在\(O(n^{5\over 3})\)的复杂度内求出所有询问的答案。
实现: 离线后排序,顺序遍历询问,先将时间转移到当前询问的时间,然后再像普通莫队一样转移区间。
排序方法: 设定块的长度为\(S_1,S_2\),按照(\(\lfloor{l\over S_1}\rfloor \lfloor{r\over S_2}\rfloor,t\))的三元组小到大排序,其中 \(t\) 表示这个询问的时刻之前经历过了几次修改操作
复杂度分析:考虑询问序列中的每个小块,小块内每个询问的一二关键字相同。在这个小块内,时间 \(t\) 最多变化 \(m\) ,对于每个询问,\(l,r\) 最多变化 \(S_1,S_2\), 一共右\(n^2\over {S_1,S_2}\) 个这样的块,相邻块之间转移复杂度是\(O(n)\), 总复杂度就是
\(O(mS_1+mS_2+{n^2m\over S_1S_2}+{n^3\over S_1S_2})\)
当\(n,m\)同阶时,取\(S_1 = S_2 = n^{2\over 3}\) 时可达到最优复杂度\(O(n^{5\over 3})\)
int l = 0, r = 0, t = 0, nowAns = 0;
inline void move(int pos, int sign) {
// update nowAns
}
inline void moveTime(int t, int sign) {
// apply or revoke modification
// update nowAns
}
void solve() {
BLOCK_SIZE = int(ceil(pow(n, 2.0 / 3)));
sort(querys, querys + m);
for (int i = 0; i < q1; ++i) {
const query q = querys[i];
while (t < q.t) moveTime(t++, 1);
while (t > q.t) moveTime(--t, -1);
while (l < q.l) move(l++, -1);
while (l > q.l) move(--l, 1);
while (r < q.r) move(r++, 1);
while (r > q.r) move(--r, -1);
ans[q.id] = nowAns;
}
}
原文地址:https://www.cnblogs.com/1625--H/p/11329586.html