5.9 Day5

莫队算法

前记:
最近因为关注勇士火箭的系列赛,比较颓废,白天没有好好打模拟赛,晚上也没有好好刷题学算法,而且还春困,所以算法学习较慢。昨天开始学莫队,到现在为止,还只学会了普通莫队,带修莫队和树上莫队,还需学习树上带修莫队,了解回滚莫队。今晚应该会熬夜学会树上带修莫队,博客会持续更新。
昨天讲到了分块(只有稍微一点点),而莫队思想中也有分块!!!
莫队是一种解决离线查询区间内信息的优秀暴力算法,需要通过卡常等来优化复杂度实现得到高分。下面直接放代码,很多细节会有注释。
普通莫队:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,t,now,nowl,nowr;
int a[N],cnt[N];
bool ans[N];
struct number{int x,y,id;}num[N];

inline bool cmp(number a,number b)
{
	if (a.x/t<b.x/t) return true;
	if (a.x/t==b.x/t && a.y<b.y) return true;
	return false;	
}

inline int add(int pos)
{
	cnt[a[pos]]++;
	if (cnt[a[pos]]==1) now++;	
}

inline int del(int pos)
{
	cnt[a[pos]]--;
	if (cnt[a[pos]]==0) now--;	
}

int main(){
	scanf("%d%d",&n,&m);
	for (register int i=1; i<=n; ++i) scanf("%d",&a[i]); 
	t=(int)sqrt(n);
	for (register int i=1; i<=m; ++i) scanf("%d%d",&num[i].x,&num[i].y),num[i].id=i;
	
	sort(num+1,num+m+1,cmp);
	
	for (register int i=1; i<=m; ++i)
	{
		// [nowl,nowr]区间内的now值已经算完了,如果nowl小于当前的左区间num[i].x,那么就得把 下标nowl 的贡献先减去,然后 nowl++
		// 如果 nowl大于当前的左区间num[i].x ,那么,由于 下标nowl的贡献已经包括在now中了,且此刻now需要加上 下标nowl-1 的贡献
		//所以先 nowl--,在加上这个下标的贡献 
		// nowr与nowl相同 
		while (nowl<num[i].x) del(nowl),nowl++;
		while (nowl>num[i].x) nowl--,add(nowl);
		while (nowr<num[i].y) nowr++,add(nowr);
		while (nowr>num[i].y) del(nowr),nowr--;
		if (now==num[i].y-num[i].x+1) ans[num[i].id]=true;
	}
	
	for (register int i=1; i<=m; ++i) if (ans[i]) puts("Yes"); else puts("No");
return 0;
}
带修莫队:
#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int n,m,sum,summ,nowl,nowr,nowt,now,t;
int a[N],cnt[1000005],ans[N];
char c;
struct number{int x,y,t,id;}num[N];
struct number2{int pos,val;}num2[N];

inline bool cmp(number a,number b)
{
	if (a.x/t<b.x/t) return true;
	if (a.x/t==b.x/t && a.y<b.y) return true;
	if (a.x/t==b.x/t && a.y/t==b.y/t && a.t<b.t) return true;
	return false;	
}

inline void add(int pos)
{
	cnt[a[pos]]++;
	if (cnt[a[pos]]==1) now++; 
}

inline void del(int pos)
{
	cnt[a[pos]]--;	
	if (cnt[a[pos]]==0) now--;
}

inline void upt(int x)
{
	if (nowl<=num2[x].pos && num2[x].pos<=nowr)
	{
		cnt[a[num2[x].pos]]--;
		if (cnt[a[num2[x].pos]]==0) now--;
		cnt[num2[x].val]++;
		if (cnt[num2[x].val]==1) now++;
	}
	swap(a[num2[x].pos],num2[x].val);
}

int main(){
	scanf("%d%d",&n,&m);
	t=(int)sqrt(n);
	for (register int i=1; i<=n; ++i) scanf("%d",&a[i]);
	for (register int i=1; i<=m; ++i)
	{
		cin>>c;
		if (c=='Q')
		{
			sum++;
			scanf("%d%d",&num[sum].x,&num[sum].y);
			num[sum].t=summ;
			num[sum].id=sum;
		}
		else
		{
			summ++;
			scanf("%d%d",&num2[summ].pos,&num2[summ].val);
		}
	}
	
	sort(num+1,num+sum+1,cmp);
	
	for (register int i=1; i<=sum; ++i)
	{
		while (nowl<num[i].x) del(nowl),nowl++;
		while (nowl>num[i].x) nowl--,add(nowl);
		while (nowr<num[i].y) nowr++,add(nowr);
		while (nowr>num[i].y) del(nowr),nowr--;
		while (nowt<num[i].t) nowt++,upt(nowt);
		while (nowt>num[i].t) upt(nowt),nowt--;
		ans[num[i].id]=now;
	}
	for (register int i=1; i<=sum; ++i) printf("%d\n",ans[i]);
return 0;	
}
树上莫队:
#include <bits/stdc++.h>
using namespace std;
const int N=4e4+5,M=1e5+5;
int n,m,noww,t,len,u,v,x,y,id,LCA;
int nowl,nowr,now;
int a[N],b[N],b1[N],ans[M];
int d[N],p[N][21],bb[N<<1],ru[N],chu[N];
int sum[N],used[N];
int cnt,head[N];
struct edge{int next,to;}e[N<<1];
struct number{int x,y,id,lca;}num[M];

inline void add(int u,int v)
{
    cnt++;
    e[cnt].next=head[u];
    e[cnt].to=v;
    head[u]=cnt;	
}

inline bool cmp(number a,number b)
{
    if (a.x/t<b.x/t) return true;
    if (a.x/t==b.x/t && a.y<b.y) return true;
    return false;	
}

void dfs(int u,int fa)
{
    id++; ru[u]=id; bb[id]=u;
    for (register int i=1; (1<<i)<=d[u]; ++i) p[u][i]=p[p[u][i-1]][i-1];
    for (register int i=head[u]; i; i=e[i].next)
    if (e[i].to!=fa)
    {
        d[e[i].to]=d[u]+1;
        p[e[i].to][0]=u;
        dfs(e[i].to,u);	
    }
    id++; chu[u]=id; bb[id]=u;
}

inline int lca(int a,int b)
{
    if (d[a]>d[b]) swap(a,b);
    for (register int i=20; i>=0; --i) if (d[b]-(1<<i)>=d[a]) b=p[b][i];
    if (a==b) return a;
    for (register int i=20; i>=0; --i) 
    if (p[a][i]==p[b][i]) continue;
    else a=p[a][i],b=p[b][i];
    return p[a][0];
}

inline void add(int pos)
{
    sum[a[pos]]++;
    if (sum[a[pos]]==1) now++;	
}

inline void del(int pos)
{
    sum[a[pos]]--;
    if (sum[a[pos]]==0) now--;	
}

inline void upt(int pos)
{
    if (!used[bb[pos]]) add(bb[pos]); else del(bb[pos]);
    used[bb[pos]]^=1; 	
}

int main(){
    scanf("%d%d",&n,&m);
    t=(int)sqrt(n);
    for (register int i=1; i<=n; ++i) scanf("%d",&a[i]),b1[i]=a[i];
    
    sort(b1+1,b1+n+1);
    len=1; b[len]=b1[1]; noww=b1[1];
    for (register int i=2; i<=n; ++i) if (b1[i]!=noww) len++,b[len]=b1[i],noww=b1[i];
    for (register int i=1; i<=n; ++i) a[i]=lower_bound(b+1,b+len+1,a[i])-b;
    
    for (register int i=1; i<n; ++i) scanf("%d%d",&u,&v),add(u,v),add(v,u);
    dfs(1,0);

    for (register int i=1; i<=m; ++i)
    {
        scanf("%d%d",&x,&y); 
        num[i].id=i;
        if (d[x]>d[y]) swap(x,y);
        LCA=lca(x,y);
        if (LCA==x) num[i].x=ru[x],num[i].y=ru[y],num[i].lca=0;
        // 如果两个点的lca为它们两个中的一个,那么 num[i].x就是深度小的点的ru, num[i].y就是深度大的点的ru 
        else num[i].x=chu[x],num[i].y=ru[y],num[i].lca=LCA;  
        //如果两个点的lca不为它们两个中的一个,那么num[i].x就是深度小的点的chu,num[i].y就是深度大的点的ru 
    }
    sort(num+1,num+m+1,cmp);
    
    nowl=1;
    for (register int i=1; i<=m; ++i)
    {
        while (nowl<num[i].x) upt(nowl),nowl++;   
        while (nowl>num[i].x) nowl--,upt(nowl);
        while (nowr<num[i].y) nowr++,upt(nowr);
        while (nowr>num[i].y) upt(nowr),nowr--;
        if (num[i].lca) 
        {	
            if (!used[num[i].lca]) add(num[i].lca); else del(num[i].lca);
            used[num[i].lca]^=1;	
        }
        ans[num[i].id]=now;
        if (num[i].lca) 
        {	
            if (!used[num[i].lca]) add(num[i].lca); else del(num[i].lca);
            used[num[i].lca]^=1;	
        }
    }
    
    for (register int i=1; i<=m; ++i) printf("%d\n",ans[i]);
return 0;
}
…未完待续
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值