莫队/树状数组+离线 :HH的项链 及莫队例题

​​​​​​项链

大意:

一段数组,每次查询一个区间,询问区间内的数字种类数。

思路:
这道题其实做法比较多

莫队/主席树(当然我还不会。。。)/树状数组都可以。

先讲一下树状数组的做法:

采用离线+树状数组求前缀和

因为询问比较多,在线做的话用树状数组属实是扛不住,所以离线,然后按r从小到大排序。

那么对于每一次从查询【l,r】,以及我们当前查到的w:

如果w对应的数k之前是没有出现过的,那么直接将这一点w的权值记为1即可,后面树状数组维护前缀和即可。但k如果之前在w'位置出现过,那么我们就将这一点w的权值记为1,bing将w’的权值记为0.因为w'的权值贡献在之前的查询中肯定是计算过了,但它不一定对当前的【l,r】区间有贡献,但我们能确定w一定对【l,r】有贡献,所以做如上操作。并且由于是按查询的r从小到大排序,所以将w’的权值记为0对后面没有影响。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) x&(-x)
const ll N=1e6+10;
ll tr[N];
struct ty
{
	ll id;//编号 
	ll l,r;//左右界 
}num[N];
ll ans[N];
bool cmp(ty a,ty b){
	return a.r<b.r;
}
ll n,m;
ll a,b;
ll id[N];
void add(ll x,ll y)
{
	while(x<=n)
	{
		tr[x]+=y;
		x+=lowbit(x);
	}
}
ll sum(ll x)
{
	ll ans=0;
	while(x>0)
	{
		ans+=tr[x];
		x-=lowbit(x);
	}
	return ans;
}
int main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%lld",&id[i]);
	}
	scanf("%lld",&m);
	for(int i=1;i<=m;++i)
	{
		scanf("%lld%lld",&a,&b);
		num[i]={i,a,b};
	}
	sort(num+1,num+1+m,cmp);
	map<ll,ll> mp;
	for(int i=1,j=1;i<=m;++i)
	{
	    ll l=num[i].l,r=num[i].r,idd=num[i].id;
		while(j<=r)
		{
			if(mp[id[j]]) add(mp[id[j]],-1);
			mp[id[j]]=j;
			add(j,1);
			j++;
		}	
		ans[idd]=sum(r)-sum(l-1);
	}
	for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
	return 0;
}

然后是莫队的做法:

莫队 

那么这个题对于莫队来说就是一道板子题了。。。

就硬套板子就行了。

注意分块别写错,以及移动l,r时的小trick

//在当前范围内,是先移动再处理
while(l>num[i].l) move(--l,1);//--l
while(r<num[i].r) move(++r,1);//++r

code: 

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) x&(-x)
const ll N=1e6+10;
ll tr[N];
ll n,m;
struct ty
{
	ll id;//编号 
	ll l,r;//左右界 
}num[N];
ll wid[N];//记录种类 
ll id[N];//分块用 
ll ans[N];//记录答案 
ll cnt[N];//记录每一个种类出现的次数 
bool cmp(ty a,ty b){
	if(id[a.l]==id[b.l])
	return a.r<b.r;
	return a.l<b.l;
}
ll anss=0;
void block()
{
	ll kk=sqrt(n);
	for(int i=1;i<=n;++i)
	{
		id[i]=(i-1)/kk+1;
	 } 
} 
void move(ll pos/*当前位置*/,ll op)
{
	if(op==0)
	{
		cnt[wid[pos]]--;
		if(cnt[wid[pos]]==0) anss--; 
		return;
	} 
	if(op==1)
	{
		cnt[wid[pos]]++;
		if(cnt[wid[pos]]==1) anss++;
		return;
	}
	
}
inline int read(){
    int sgn = 1; int cnt = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if(ch == '-')
            sgn = -sgn;
        ch = getchar();
    }
    while ('0' <= ch && ch <= '9') {
        cnt = cnt*10 + (ch-'0');
        ch = getchar();
    }
    return  sgn*cnt;
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)
	{
		wid[i]=read();
	}
	block();
	m=read();
    ll a,b;
	for(int i=1;i<=m;++i)
	{
		a=read();
        b=read();
		num[i]={i,a,b};
	}
	sort(num+1,num+1+m,cmp);
	ll l=0,r=0;
    anss=0;
	for(int i=1;i<=m;++i)
	{
		while(l<num[i].l) move(l++,0);
		while(l>num[i].l) move(--l,1);//--l
		while(r<num[i].r) move(++r,1);//++r
		while(r>num[i].r) move(r--,0);
		ans[num[i].id]=anss;
	}
	for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
	return 0;
}

以及,这题有输入输出挂。 

到这里这题就算结束了,再上一道莫队的题。

Powerful array - CodeForces 86D - Virtual Judge

大意差不多,就是查询的东西不一样

每次查询区间【l,r】,每一个数m出现的次数为k,则其贡献为k^2*m,就区间总贡献。

那么每次数字的次数+1时,贡献就加上m*(2*k+1)

每次数字的次数-1时,贡献就减去m*(2*k-1)

改变一下移动的处理操作即可。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) x&(-x)
const ll N=1e6+10;
ll tr[N];
ll n,m;
struct ty
{
	ll id;//编号 
	ll l,r;//左右界 
}num[N];
ll wid[N];//记录种类 
ll id[N];//分块用 
ll ans[N];//记录答案 
ll cnt[N];//记录每一个种类出现的次数 
bool cmp(ty a,ty b){
	if(id[a.l]==id[b.l])
	return a.r<b.r;
	return a.l<b.l;
}
ll anss=0;
void block()
{
	ll kk=sqrt(n);
	for(int i=1;i<=n;++i)
	{
		id[i]=(i-1)/kk+1;
	 } 
} 
void move(ll pos/*当前位置*/,ll op)
{
	if(op==0)
	{
		anss-=(cnt[wid[pos]]*2-1)*wid[pos];
		cnt[wid[pos]]--;
		
		return;
	} 
	if(op==1)
	{
		anss+=(cnt[wid[pos]]*2+1)*wid[pos];
		cnt[wid[pos]]++;
		return;
	}
	
}
inline int read(){
    int sgn = 1; int cnt = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if(ch == '-')
            sgn = -sgn;
        ch = getchar();
    }
    while ('0' <= ch && ch <= '9') {
        cnt = cnt*10 + (ch-'0');
        ch = getchar();
    }
    return  sgn*cnt;
}
int main()
{
	n=read();
	m=read();
	for(int i=1;i<=n;++i)
	{
		wid[i]=read();
	}
	block();
    ll a,b;
	for(int i=1;i<=m;++i)
	{
		a=read();
        b=read();
		num[i]={i,a,b};
	}
	sort(num+1,num+1+m,cmp);
	ll l=0,r=0;
    anss=0;
	for(int i=1;i<=m;++i)
	{
		while(l<num[i].l) move(l++,0);
		while(l>num[i].l) move(--l,1);//--l
		while(r<num[i].r) move(++r,1);
		while(r>num[i].r) move(r--,0);
		ans[num[i].id]=anss;
	}
	for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值