NC15557 连续区间的最大公约数

链接

思路:

        主要考虑子区间gcd等于区间gcd的数量怎么求。gcd有一个性质:越多的数的gcd一定是单调不上升的。也就是说父亲区间的gcd一定是整个父亲区间里任何子区间的最小gcd,包括左右儿子区间的gcd。这题记录子区间gcd与区间gcd相等的数量和记录不相等的数量是一样的。

        以记录不相等的数量为例。

        对于一个区间长度为len的区间,它的所有连续子区间方法数是(len+1)*len/2,也就是子区间长度为1的数量一直加到子区间长度为len的数量。我们考虑区间不相等gcd子区间数量怎么合并。分为三部分:

        1.只包含左区间

        2.只包含右区间

        3.左右区间都包含

        前两种是一样的。我们记父亲区间是u,左儿子区间是lu,右儿子区间是ru。如果u.gcd==lu.gcd,那么lu的贡献就是lu.nog_cnt,因为lu.nog_cnt就是只包括左区间的gcd!=u.gcd的数量,同理右区间贡献就是ru.nog_cnt。如果u.gcd!=lu.gcd,那么lu的贡献就是(l.len+1)*l.len/2,因为lu.gcd肯定是大于u.gcd的,gcd具有单调性。当lu.gcd都大于u.gcd的时候,也就是说无论lu的子区间是什么gcd都是大于u.gcd,所以子区间的贡献就是(l.len+1)*l.len/2。右区间同理。

        接下来我们考虑跨左右区间的贡献。假如我们固定了左区间的位置idxl,我们考虑右区间有贡献的位置idxr。由于gcd具有单调性,区间范围越大gcd一定不上升,也就是说只有当idxr变小时,区间(idxl,idxr)的gcd才有可能变大。同时u.gcd又是区间最小gcd,所以只要idxr减小到区间(idxl,idxr)的gcd大于u.gcd时,(0,idxr)区间的idxr都是合法的,也就是这时候对于某个固定的idxl它的贡献就是(0,idxr)的大小。

        接下来我们考虑idxl的移动。同样的因为gcd的单调性,如果idxl减小的时候,区间(idxl,idxr)的gcd一定是单调不上升的,也就是说当idxl减小时,区间(idxl,idxr)的gcd又可能恢复到u.gcd,这时候我们只需要继续让idxr减小即可。

        容易发现对于idxr来说,随着idxl的减小,idxr也一定是减小的,所以我们就可以用双指针的方式,用o(n)的时间来遍历每个idxl的值,求出每个idxl的贡献,最后它们的贡献和就是跨区间的贡献。在遍历idxl和idxr的时候,我们可以根据gcd的性质来稍微优化一下,如果区间(l,r)的gcd是一样的,我们就可以将它们当成一个值,因为这整个区间的gcd是一样。我们用pair<gcd,cnt>来表示某个连续的gcd的值有多少个。

        在合并区间的时候,我们需要用到左区间从右端点到idxl的区间gcd,我们即为ne,和右区间的左端点到idxr的区间gcd,我们即为pre,我们可以在合并的时候一起将pre和ne更新。

        接下来我们考虑pre和ne怎么更新。仍然记父亲区间为u,左儿子为lu,右儿子为ru。pre表示的是区间的左端到idxl的区间gcd,所以对于u.pre的左半区间我们可以直接将lu.pre赋值给u.pre,这部分区间是已经求过的了。对于剩下的ru区间我们从到右遍历一次,我们仍然是将gcd一样的值用一个pair<gcd,cnt>来记录,也就是说如果gcd(l,idxl+1)==gcd(l,idxl),我们就让pair<gcd(l,idxl),cnt>的cnt++。如果gcd(l,idxl+1)!=gcd(l,idxl),我们就新开一个pair记为<gcd(l,idxl+1),1>。ne同理。

        nog_cnt的代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int inf=0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
//#define int long long
//const ll P=2281701377;
const ll P=998244353;
const int mod=1e9+7;
#define fi first
#define se second

ll gcd(ll a,ll b){
	return b==0?a:gcd(b,a%b);
}

int n,m;
int a[N];

struct node{
	int l,r;
	int len;
	int g;
	ll nog_cnt;
	vector<pii> pre,ne;
}tr[N*4];

void pushup(node &u,node &l,node &r){
	u.len=l.len+r.len;
	u.g=gcd(l.g,r.g);
	u.nog_cnt=0;
	u.nog_cnt+=(l.g==u.g?l.nog_cnt:(ll)(l.len+1)*l.len/2);
	u.nog_cnt+=(r.g==u.g?r.nog_cnt:(ll)(r.len+1)*r.len/2);
	ll sum=0;
	for(int i=0;i<r.pre.size();i++)
		sum+=r.pre[i].se;
	int idx=r.pre.size()-1;
	ll ans=0;
	for(int i=0;i<l.ne.size();i++){
		while(idx>=0&&gcd(l.ne[i].fi,r.pre[idx].fi)==u.g){
			sum-=r.pre[idx].se;
			idx--;
		}
		ans+=(ll)sum*l.ne[i].se;
	}
	u.nog_cnt+=ans;
	u.pre=l.pre;
	u.ne=r.ne;
	for(int i=0;i<r.pre.size();i++){
		if(r.pre[i].fi%u.pre.back().fi==0)
			u.pre.back().se+=r.pre[i].se;
		else
			u.pre.push_back({gcd(u.pre.back().fi,r.pre[i].fi),r.pre[i].se});
	}
	for(int i=0;i<l.ne.size();i++){
		if(l.ne[i].fi%u.ne.back().fi==0)
			u.ne.back().se+=l.ne[i].se;
		else
			u.ne.push_back({gcd(u.ne.back().fi,l.ne[i].fi),l.ne[i].se});
	}
}

void build(int u,int l,int r){
	tr[u]={l,r};
	tr[u].nog_cnt=0;
	tr[u].pre.clear();
	tr[u].ne.clear();
	if(l==r){
		tr[u]={l,r,1,a[l],0};
		tr[u].pre.push_back({a[l],1});
		tr[u].ne.push_back({a[l],1});
		return;
	}
	int mid=(l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}

node query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r){
		return tr[u];
	}
	int mid=tr[u].l+tr[u].r>>1;
	if(r<=mid) return query(u<<1,l,r);
	else if(l>mid) return query(u<<1|1,l,r);
	else{
		node no1=query(u<<1,l,r);
		node no2=query(u<<1|1,l,r);
		node ans;
		pushup(ans,no1,no2);
		return ans;
	}
}

int cas=0;
void solve(){
	cout<<"Case #"<<++cas<<":"<<endl;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	build(1,1,n);
	cin>>m;
	while(m--){
		int l,r;
		cin>>l>>r;
		node no=query(1,l,r);
		ll ans1=no.g;
		ll ans2=(ll)(no.len+1)*no.len/2-no.nog_cnt;
		cout<<ans1<<' '<<ans2<<endl;
	}
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--){
        solve();
    }

}

        要是改成记录相等数量的时候,我们记父亲区间为u,左儿子为lu,右儿子为ru。如果lu.gcd==u.gcd,贡献仍然是lu.g_cnt,如果lu.gcd!=u.gcd,贡献就是0,因为无论lu子区间怎么取,子区间的gcd都是大于u.gcd的。右区间同理。

          跨区间的时候,同样是利用gcd的单调性。因为我们是求gcd相等的情况,也就是说区间越大gcd会越小,所以我们这次让idxl从小到大,idxr从小到大。这样当我们先从固定idxl取到一个最大idxr时,idxr的右边都是符合条件的。当idxl变大时,idxr可能会变大,如果变大了idxr向右移即可。同样是利用gcd的单调性来进行双指针。

        g_cnt的代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int inf=0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
//#define int long long
//const ll P=2281701377;
const ll P=998244353;
const int mod=1e9+7;
#define fi first
#define se second

ll gcd(ll a,ll b){
	return b==0?a:gcd(b,a%b);
}

int n,m;
int a[N];

struct node{
	int l,r;
	int len;
	int g;
	ll g_cnt;
	vector<pii> pre,ne;
}tr[N*4];

void pushup(node &u,node &l,node &r){
	u.len=l.len+r.len;
	u.g=gcd(l.g,r.g);
	u.g_cnt=0;
	u.g_cnt+=(l.g==u.g?l.g_cnt:0);
	u.g_cnt+=(r.g==u.g?r.g_cnt:0);
	ll sum=0;
	for(int i=0;i<r.pre.size();i++)
		sum+=r.pre[i].se;
	int idx=0;
	ll ans=0;
	ll t=0;
	for(int i=l.ne.size()-1;i>=0;i--){
		while(idx<r.pre.size()&&gcd(l.ne[i].fi,r.pre[idx].fi)!=u.g){
			//sum-=r.pre[idx].se;
			t+=r.pre[idx].se;
			idx++;
		}
		ans+=(ll)(sum-t)*l.ne[i].se;
	}
	u.g_cnt+=ans;
	u.pre=l.pre;
	u.ne=r.ne;
	for(int i=0;i<r.pre.size();i++){
		if(r.pre[i].fi%u.pre.back().fi==0)
			u.pre.back().se+=r.pre[i].se;
		else
			u.pre.push_back({gcd(u.pre.back().fi,r.pre[i].fi),r.pre[i].se});
	}
	for(int i=0;i<l.ne.size();i++){
		if(l.ne[i].fi%u.ne.back().fi==0)
			u.ne.back().se+=l.ne[i].se;
		else
			u.ne.push_back({gcd(u.ne.back().fi,l.ne[i].fi),l.ne[i].se});
	}
}

void build(int u,int l,int r){
	tr[u]={l,r};
	tr[u].g_cnt=0;
	tr[u].pre.clear();
	tr[u].ne.clear();
	if(l==r){
		tr[u]={l,r,1,a[l],1};
		tr[u].pre.push_back({a[l],1});
		tr[u].ne.push_back({a[l],1});
		return;
	}
	int mid=(l+r)>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}

node query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r){
		return tr[u];
	}
	int mid=tr[u].l+tr[u].r>>1;
	if(r<=mid) return query(u<<1,l,r);
	else if(l>mid) return query(u<<1|1,l,r);
	else{
		node no1=query(u<<1,l,r);
		node no2=query(u<<1|1,l,r);
		node ans;
		pushup(ans,no1,no2);
		return ans;
	}
}

int cas=0;
void solve(){
	cout<<"Case #"<<++cas<<":"<<endl;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	build(1,1,n);
	cin>>m;
	while(m--){
		int l,r;
		cin>>l>>r;
		node no=query(1,l,r);
		ll ans1=no.g;
		ll ans2=no.g_cnt;
		cout<<ans1<<' '<<ans2<<endl;
	}
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--){
        solve();
    }

}

  • 15
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值