您的位置:首页 > 其它

莫队算法

2016-01-27 14:54 363 查看
莫队算法:离线的询问多个区间。离线,可以理解为没有修改。首先对询问的区间进行排序,排序时先以左端点按块排序及l/n,n就是左右区间的大小开根号。再按有段点排序。四个while循环给你一小段区间,左端点不动的条件下,左右移动右端点。同样,右端点不动,左右移动左端点。具体处理方法由题目决定。

例1:http://www.lydsy.com/JudgeOnline/problem.php?id=2038
小z的袜子
给N个袜子,编号代表不同的颜色。从L到R闭区间任意抽取两只,颜色一样的概率
分母显然实C(2,R-L+1).分子用莫队算法求和计算。
为防超范围还是用long long
<span style="font-size:14px;">#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;
int n,m;
int a[50005];
long long cnt[50005];
long long ans[50005];
struct T
{
int l,r,id;
bool operator < (const T a)const
{
if(a.l/222 == l/222)
return r < a.r;
return l < a.l;
}
}t[50005],h[50005];
long long gcd(long long a,long long b)
{
if(b == 0)
return a;
else
return gcd(b,a % b);
}
long long fun(long long x)
{
return x * ( x - 1 ) / 2;
}
int main()
{
//cout<<sqrt(50005)<<endl;
//freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i ++)
{
scanf("%d",&a[i]);
}
for(int i = 0; i < m; i ++)
{
scanf("%d%d",&t[i].l,&t[i].r);
h[i].l = t[i].l;
h[i].r = t[i].r;
t[i].id = i;
}
sort(t, t + m);
int R = 1,L = 1;
cnt[a[1]] ++;
long long ant = 0;
for(int i = 0; i < m; i ++)
{
while(R < t[i].r)
{
R ++;
ant -= fun(cnt[a[R]]);
cnt[a[R]] ++;
ant += fun(cnt[a[R]]);
}
while(R > t[i].r)
{
ant -= fun(cnt[a[R]]);
cnt[a[R]] --;
ant += fun(cnt[a[R]]);
R --;
}
//cout<<ant<<endl;
while(L > t[i].l)
{
L --;
ant -= fun(cnt[a[L]]);
cnt[a[L]] ++;
ant += fun(cnt[a[L]]);
}
while(L < t[i].l)
{
ant -= fun(cnt[a[L]]);
cnt[a[L]] --;
ant += fun(cnt[a[L]]);
L ++;
}

ans[t[i].id] = ant;
//cout<<ant<<endl;
}
long long fenmu;
long long temp;
long long yue;
for(int i = 0; i < m; i ++)
{
temp = h[i].r - h[i].l + 1;
fenmu  = fun(temp);
yue = gcd(fenmu,ans[i]);
printf("%lld/%lld\n",ans[i]/yue,fenmu/yue);
}
return 0;
}</span><span style="font-size: 24px;">
</span>


例题1:http://codeforces.com/contest/617/problem/E

E. XOR and Favorite Number

题意:第一行n,m,k.数组的大小n,m次询问一个区间l,r内 l ≤ i ≤ j ≤ r 并且异或
 ai, ai + 1, ..., aj 等于 k.

此处用到异或的特点:x^x^y
=y

先进行预处理求出每个点i,从1到i的异或值存为sum[i] 。这样sum[l-1]^sum[r]表示从a[l]异或到a[r].由此可知当sum[i]^k就能算出一个数。这个数……sum[i]
== k.ans加上该数在之前出现的次数。每一次加,其实加的是区间的个数。此题要注意加值的时候先加ans,再加cnt,减的时候相反

看了n多种代码了,考虑了好几天。。。。。

codeforce题目很多都卡边界,一般都要开到long long.懒得想就都开long long了。

<span style="font-size:14px;">//莫队算法。四个while。先根据l排序,再根据right排序。为了避免超时,按块排序。及按l/400排序
//莫队算法需要注意的是增加的时候,先加ans,再加cnt。减的时候,先减cnt,再减ant
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;
int n,m,temp;
long long k;
long long sum[1100000];
long long cnt[1100000] = {0};
long long  a[1100000];
struct T
{
int L,R,id;
bool operator < (const T a)const
{
if(L/400 == a.L/400)
{
return R < a.R;
}
return L < a.L;
}
}t[100005];
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d%I64d",&n,&m,&k);
for(int i = 1; i <= n; i ++)
{
scanf("%I64d",&sum[i]);
if(i != 1)
sum[i] = sum[i-1] ^ sum[i];
}
for(int i = 0; i < m; i++)
{
scanf("%d%d",&temp,&t[i].R);
t[i].L = temp - 1;
t[i].id = i;
}
sort(t,t+m);
int R = 0,L = 0;
cnt[0] = 1;
long long ans = 0;
for(int i = 0; i < m; i ++)
{
while(R < t[i].R)
{
R ++;
ans += cnt[sum[R] ^ k];
cnt[sum[R]] ++;
}
while(R > t[i].R)
{
cnt[sum[R]] --;
ans -= cnt[sum[R] ^ k];
R --;
}
// cout<<ans<<endl;
while(L > t[i].L)
{
L --;
ans += cnt[sum[L] ^ k];
cnt[sum[L]] ++;
}
while(L < t[i].L)
{
cnt[sum[L]] --;//先把cnt减了
ans -= cnt[sum[L] ^ k];
L ++;
}
a[t[i].id] = ans;
}
for(int i = 0; i < m; i ++)
{
cout<<a[i]<<endl;
}
return 0;
}</span>

失败的莫队算法
http://codeforces.com/contest/622/problem/C

C. Not Equal on a Segment(思维)

给一个数组

询问m次,给出左端点和右端点,和x,求在区间内和x不等的位置

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <list>
#include <map>
#include <stack>
#include <vector>
#include <cstring>
#include <sstream>
#include <string>
#include <set>
using namespace std;
int n,m;
int a[200004];
set<int>k;
struct t
{
int l,r,x,id;
bool operator < (const t a)const
{
if(a.l / 1000 == l / 1000)
{
return a.r > r;
}
else
return a.l/1000 > l / 1000;
}
} s[200004];
int ans[200004];
int cnt[1000002] = {0};
int num[1000004];
int main()
{
//freopen("in.txt","r",stdin);

scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i ++)
{
scanf("%d",&a[i]);
}
for(int i = 0; i < m; i ++)
{
scanf("%d%d%d",&s[i].l,&s[i].r,&s[i].x);
s[i].id = i;
}
sort(s,s + m);
k.clear();
int l = 1,r = 1;
cnt[a[1]] ++;
num[a[1]] = 1;
k.insert(a[1]);
for(int i = 0; i < m; i ++)
{
while(s[i].r > r)
{
r ++;
if(r >= s[i].l && r <= s[i].r)
num[a[r]] = r;
if(cnt[a[r]] == 0)
{

k.insert(a[r]);
}
cnt[a[r]] ++;
}
while(s[i].r < r)
{
if(cnt[a[r]] == 1)
{
num[a[r]] = -1;
k.erase(a[r]);
}
cnt[a[r]] --;
r --;
if(r >= s[i].l && r <= s[i].r)
num[a[r]] = r;
}
while(s[i].l < l)
{
l --;
if(l >= s[i].l && l <= s[i].r)
num[a[l]] = l;
if(cnt[a[l]] == 0)
{

k.insert(a[l]);
}
cnt[a[l]] ++;
}

while(s[i].l > l)
{
if(cnt[a[l] ]== 1)
{
k.erase(a[l]);
num[a[l]] = -1;
}
cnt[a[l]] --;
l ++;
if(l > 44)
cout<<endl;
if(l >= s[i].l && l <= s[i].r)
num[a[l]] = l;
}
for(set<int>::iterator it = k.begin(); it != k.end(); it ++)
{
//printf("num[%d] = %d\n",*it,num[*it]);
if(*it != s[i].x)
{
if(num[*it] >= s[i].l && num[*it] <= s[i].r)
ans[s[i].id] = num[*it];
else//对于苦逼情况的处理
{
for(int t = s[i].l ; t <= s[i].r; t ++)
{
if(a[t] != s[i].x)
{
ans[s[i].id] = t;
}
}
}
}
}

}
for(int i =0; i < m; i ++)
{
if(ans[i] == 0)
ans[i] = -1;
printf("%d\n",ans[i]);
}
return 0;
}
/*8 2
2 1 2 2 1 2 2 2
2 7 2
4 8 2*/
正确做法如下

//last[i],表示i位置上和它数值不同,且在它之前的最大位置
//如果last[r] != x.r就是那个位置
//如果last[r] == x.那就要往前找离他最近的不同的位置,,那个位置就是与x不同的位置。
//最右侧,最靠右的地方
#include <iostream>
#include <cstdio>
using namespace std;
int a[1000005];
int last[1000005];
int main()
{
// freopen("in.txt","r",stdin);
int m,n,l,r,x;
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i ++)
{
scanf("%d",&a[i]);
}
last[1] = -1;
for(int i = 2; i <= n; i ++)
{
if(a[i] == a[i - 1])
last[i] = last[i - 1];
else
last[i] = i -1;
}
for(int i = 0; i < m; i ++)
{
scanf("%d%d%d",&l,&r,&x);
if(a[r] == x)
{
if(last[r] < l)
{
printf("-1\n");
}
else
printf("%d\n",last[r]);
}
else
{
printf("%d\n",r);
}
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: