bzoj 2038 莫队算法

给出n个数字,m次询问,每次询问在区间 [li,ri] 之间任选两个数字相等的概率是多少。(n,q<=50000)

在区间[l,r]中概率是:

vi=1C(2,f(i))C(2,rl+1) ∑C(2,f(i))C(2,r−l+1)(1<=i<=v)
                                                 (v表示数字值,f(i)表示数字i在区间内出现的次数)


考虑分子,

因为 C(2,x)=x2x2 C(2,x)=x2−x2

所以分子= vi=1f(i)2vi=1f(i)2

显然,∑f(i)=r−l+1(1<=i<=v)

区间[l,r+1]与区间[l,r]相比只多了一个元素z,

前式中分子的值S=S0-f(z)^2+(f(z)+1)^2-1=S0+2*f(z),同时inc(f(z)),O(1)的。

莫队算法优化:

注意到,每个区间可以抽象成平面中的点,每次转移的花费都相当与从某点到另一点的曼哈顿距离的长度。

所以我们花费的便是这些平面中的点联通的曼哈顿距离。平面点的曼哈顿最小生成树!

但是实际来说,搞定一个manhattan mst需要的时间不小,

神犇曰:分块是个好东西

确实,利用分块,我们可以实现O(nn)的时间复杂度。

1):排序,以左段点所在的块为第一关键字,以右端点为第二关键字

2):从左往右处理询问(离线)

3):不断调整l,r的位置并同时修改

type
        rec=record
            l,r,num,t:longint;
end;

var
        n,m,len,now     :longint;
        i,j             :longint;
        a               :array[0..50010] of rec;
        c,size          :array[0..50010] of int64;
        col,ans         :array[0..50010] of int64;
        tt,all          :int64;
procedure swap(var a,b:longint);
var
        c:longint;
begin
   c:=a; a:=b; b:=c;
end;

function gcd(a,b:int64):int64;
begin
   if a<b then exit(gcd(b,a)) else
    if (b=0) then exit(a) else exit(gcd(b,a mod b));
end;

procedure sort(l,r:longint);
var
        i,j:longint;
        x,y:longint;
        z:rec;
begin
   i:=l;j:=r;
   x:=a[(l+r) div 2].num;
   y:=a[(l+r) div 2].r;
   while (i<=j) do
   begin
      while (a[i].num<x) or ((a[i].num=x) and (a[i].r<y)) do inc(i);
      while (a[j].num>x) or ((a[j].num=x) and (a[j].r>y)) do dec(j);
      if (i<=j) then
      begin
         z:=a[i];a[i]:=a[j];a[j]:=z;
         inc(i);dec(j);
      end;
   end;
   if (i<r) then sort(i,r);
   if (j>l) then sort(l,j);
end;

begin
   read(n,m);
   for i:=1 to n do read(c[i]);
   len:=trunc(sqrt(m));
   for i:=1 to m do
   begin
      read(a[i].l,a[i].r);
      if (a[i].l>a[i].r) then swap(a[i].l,a[i].r);
      size[i]:=a[i].r-a[i].l+1;
      a[i].t:=i;
      a[i].num:=a[i].l div len+1;
   end;
   sort(1,m);
   //
   i:=1;
   while (i<=m) do
   begin
      now:=a[i].num;
      fillchar(col,sizeof(col),0);
      for j:=a[i].l to a[i].r do
      begin
         inc(ans[a[i].t],2*col[c[j]]);
         inc(col[c[j]]);
      end;
      inc(i);
      while (a[i].num<=now) and (i<=m) do
      begin
         ans[a[i].t]:=ans[a[i-1].t];
         for j:=a[i-1].r+1 to a[i].r do
         begin
            inc(ans[a[i].t],2*col[c[j]]);
            inc(col[c[j]]);
         end;
         if (a[i-1].l<a[i].l) then
         begin
            for j:=a[i-1].l to a[i].l-1 do
            begin
               dec(col[c[j]]);
               dec(ans[a[i].t],2*col[c[j]]);
            end;
         end else
         begin
            for j:=a[i].l to a[i-1].l-1 do
            begin
               inc(ans[a[i].t],2*col[c[j]]);
               inc(col[c[j]]);
            end;
         end;
         inc(i);
      end;
   end;
   //
   for i:=1 to m do
   begin
      if size[i]=1 then all:=1 else all:=size[i]*(size[i]-1);
      tt:=gcd(all,ans[i]);
      writeln(ans[i] div tt,'/',all div tt);
   end;
end.

——by Eirlys




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值