【莫队算法】小z的袜子

题目: 给定 n 个数 a 1 , a 2 …… a n m 个询问 (L,R) 。对于每个询问,从 a L , a L+1 …… a R R-L+1 个数中随机取出两个数,求这两个数相同的概率。

假设我们当前处理了询问(l1,r1),那么下个询问(l2,r2)需要操作的次数是|l1-l2|+|r1-r2|,其实就是曼哈顿距离,那么只需求出哈密尔顿路径即可确定操作序列,但是tsp不好求,则我们求出曼哈顿距离最小生成树,可知大小不超过tsp的两倍,莫队证出按此顺序操作复杂度是o(nsqrt(m))

曼哈顿距离最小生成树据说可以分成两块做,但是我实在觉得偏序关系处理起来有问题,所以还是扫了4遍,因为不想离散,所以用了一个奇怪的顺序排的


#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
const int oo=1073741819;
int tail[50005],next[1000000],sora[1000000],a[50005],c[50005],l[50005],r[50005],x[50005],y[50005],b[5][131072];
int ll[500000],rr[500000],w[500000],f[50005],u[500000];
int tim,n,m,m1,ss,tot,p,q,low,lim;
long long ans,ans1[50005],ans2[50005],tmp;
bool v[50005];
void origin() 
{
    for (m1=1;m1<=n+2;m1<<=1) ;m1++;
    for (int i=1;i<=m+1;i++) tail[i]=i;ss=m+1,tot=0;
}
inline int cmp1(const void *i,const void *j)
{
    p=*(int *)i,q=*(int *)j;
    if (y[p]-x[p]!=y[q]-x[q]) return (y[q]-x[q])-(y[p]-x[p]);
    return x[p]-x[q];
}
inline int cmp2(const void *i,const void *j)
{
    p=*(int *)i,q=*(int *)j;
    if (y[p]+x[p]!=y[q]+x[q]) return (y[q]+x[q])-(y[p]+x[p]);
    return x[p]-x[q];
}
inline int min(int e,int a,int b)
{
    if (e<=2) return (x[a]+y[a]<=x[b]+y[b]) ? a : b;
    return (y[a]-x[a]<=y[b]-x[b]) ? a : b;
}
inline void change(int e,int x,int u)
{
    for (x+=m1;x;x>>=1) {
	b[e][x]=min(e,b[e][x],u);
	if (b[e][x]!=u) return ;
    }
}
inline int ask(int e,int l,int r)
{
    int sum=0;
    if (l>r) return 0;
    l+=m1-1,r+=m1+1;
    for (;!(1==(l^r));l>>=1,r>>=1) {
	if (0==(l&1)) sum=min(e,sum,b[e][l+1]);
	if (1==(r&1)) sum=min(e,sum,b[e][r-1]);
    }
    return sum;
}
void link(int p,int q)
{
    if (!p || !q) return ;
    tot++;
    ll[tot]=p,rr[tot]=q,w[tot]=abs(x[p]-x[q])+abs(y[p]-y[q]);
}
void mysoul()
{
    int i,xx;
    x[0]=0,y[0]=oo;
    qsort(u+1,m+1,sizeof(u[1]),cmp1);tim=1;
    for (i=1;i<=m+1;i++) {
	xx=ask(tim,x[u[i]],n);
	link(u[i],xx);
	change(tim,x[u[i]],u[i]);
    }
    tim=2;
    for (i=m+1;i>=1;i--) {
	xx=ask(tim,y[u[i]]+1,n);
	link(u[i],xx);
	change(tim,y[u[i]],u[i]);
    }
    qsort(u+1,m+1,sizeof(u[1]),cmp2);tim=3;
    for (i=1;i<=m+1;i++) {
	xx=ask(tim,0,x[u[i]]-1);
	link(u[i],xx);
	change(tim,x[u[i]],u[i]);
    }
    tim=4;
    for (i=m+1;i>=1;i--) {
	xx=ask(tim,y[u[i]],n);
	link(u[i],xx);
	change(tim,y[u[i]],u[i]);
    }
}
int find(int x) {if (f[x]!=x) f[x]=find(f[x]);return f[x];}
int cmp3(const void *i,const void *j) {return w[*(int *)i]-w[*(int *)j];}
long long gcd(long long a,long long b)
{
    long long t;
    for (;a%b;) {
	t=a%b;
	a=b;
	b=t;
    }
    return b;
}
inline void del(int l,int r) {for (int i=l;i<=r;i++) c[a[i]]--,ans-=c[a[i]];}
inline void add(int l,int r) {for (int i=l;i<=r;i++) ans+=c[a[i]],c[a[i]]++;}
void dfs(int x)
{
    int i,ne;
    if (r[x]>lim) add(lim+1,r[x]);
    if (l[x]<low) add(l[x],low-1);
    if (r[x]<lim) del(r[x]+1,lim);
    if (l[x]>low) del(low,l[x]-1);
    low=l[x],lim=r[x];
    ans1[x]=ans,v[x]=1;
    for (i=x;next[i]!=0;) {
	i=next[i],ne=sora[i];
	if (!v[ne]) dfs(ne);
    }
}
void link2(int x,int y)
{
    ss++,next[tail[x]]=ss,tail[x]=ss,sora[ss]=y;
    ss++,next[tail[y]]=ss,tail[y]=ss,sora[ss]=x;
}
void init()
{
    int i,ls,rs;
    scanf("%d%d\n",&n,&m);
    origin();
    for (i=1;i<=n;i++) scanf("%d",&a[i]);
    for (i=1;i<=m;i++) {
	scanf("%d%d\n",&l[i],&r[i]);
	x[i]=l[i],y[i]=r[i]+1;
	ans2[i]=((long long)r[i]-l[i]+1)*(r[i]-l[i])/2;
	u[i]=i;
    }
    l[m+1]=r[m+1]=x[m+1]=y[m+1]=0,u[m+1]=m+1;
    mysoul();
    for (i=1;i<=tot;i++) u[i]=i;
    qsort(u+1,tot,sizeof(u[1]),cmp3);
    for (i=1;i<=m+1;i++) f[i]=i;
    for (i=1,tmp=m;i<=tot && tmp;i++) {
	ls=find(ll[u[i]]),rs=find(rr[u[i]]);
	if (ls!=rs) f[ls]=rs,tmp--,link2(ll[u[i]],rr[u[i]]);
    }
    low=lim=1,ans=0,c[a[1]]=1;
    dfs(m+1);
    for (i=1;i<=m;i++) {
	tmp=gcd(ans1[i],ans2[i]);
	printf("%I64d/%I64d\n",ans1[i]/tmp,ans2[i]/tmp);
    }
}
int main()
{
    freopen("hose.in","r",stdin);
    freopen("hose.out","w",stdout);
     init();
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值