连续区间的最大公约数(线段树 + 区间合并)(不用重载函数)

题目链接

我去,这个太太太太难了…我愿称之为究极究极究极线段树区间合并!

参考题解1
参考题解2

分析一下:

这个题最最最关键的性质就是:随着区间变大,gcd的单调递减的。

1. 
首先我们看一下第一个问,区间gcd,非常简单;
关键是如何算第二个问:区间内的子区间gcd' == gcd的子区间数;

转换一下问题:
如果我们知道了[l,r]区间内gcd' != gcd的子区间个数cnt,那么也能算出来:
答案 = 总子区间数 - (gcd' != gcd 的子区间数cnt);
即 ans = (len+1)*len/2 - cnt  (len是区间长度)

那么这个cnt要咋维护???
这里对于每个节点我们还要维护两个东西vector<PII> pre,ne;
pre:表示固定当前区间左端点,向右扩展的gcd以及数量。(fi是gcd,se是数量)
ne:表示固定当前区间右端点,向左扩展的gcd以及数量。(fi是gcd,se是数量)
比如:
10,2,8,4
pre:从左边就是<10,1>,<2,3>
ne:从右边就是<4,2>,<2,2>(前面是最大公约数,后面是每一种gcd的个数)
2. 
这样就能维护cnt了,下面考虑怎么维护:(u,lu,ru)

首先不考虑区间合并时产生的情况:
那么此时如果lu.gcd == u.gcd,那么u.cnt += lu.cnt;(这个显而易见)
如果lu.gcd != u.gcd,那么u.cnt += (lu.len+1)*lu.len/2;
(这个是为什么呢?)
注意到区间gcd的性质:区间越大,gcd越小;(u.gcd <= lu.gcd)
如果lu.gcd != u.gcd,那么lu的所有子区间的gcd都不会=u.gcd(因为区间更小了,gcd应该更大)
所以此时cnt就是lu的所有子区间;
对于ru也是如此。

然后考虑区间合并时的情况:
合并时我们看的就是lu的ne 和 ru的pre能产生多少的cnt;
假设idx1是lu.ne的指针,idx2是ru.pre的指针;
对于每一个idx1我们去找一下性质:
要注意这里u.gcd是最小的!对于idx1固定,我们idx2在最右边时所产生的gcd最有可能=u.gcd (因为区间越大,gcd越小);
那么我们要算idx1对cnt产生的贡献,也就是要让idx2往左移--,知道gcd != u.gcd 时;
这时候我们就能用乘法原理求出idx1的贡献;
那么此时idx1+1,要算此时对应的idx2',此时就不用从最右边开始往左移动了;
这里的idx2是存在单调性的!还是运用区间gcd的性质:
idx1+1对应的idx2' 一定是在 idx1对应的idx2的左边,这样gcd的值才有可能 != u.gcd;
所以我们可以用一个指针idx2,然后遍历idx1,这样就能算出合并时产生的cnt了;
3. 
下面考虑pre和ne在pushup时要怎么维护?

首先考虑区间没合并时,这个简单,看代码;
其次考虑区间合并时:
也就是左子节点左端点固定,右端点要一直扩展到右子节点的右端点;
这个还是看代码吧,真的不知道咋说了...

在分析的时候一定要画图去看!!!

代码

#include<iostream>
#include<queue>
#include<cstring>
#include<vector>
#include<stdio.h>
#include<map>
#include<algorithm>
#include<deque>
#include<stack>
#include<set>
// #include <unordered_map>
#include<math.h>
#include<string.h>
#define IOS ios::sync_with_stdio(false),cin.tie(0);
using namespace std;
 
#define pb push_back
#define coutl cout<<"------------"<<endl;
#define fi first
#define se second
  
#define ire(x) scanf("%d",&x)
#define iire(a,b) scanf("%d %d",&a,&b)
#define lre(x) scanf("%lld",&x)
#define llre(a,b) scanf("%lld %lld",&a,&b)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define endl "\n"
#define PI acos(-1.0)

typedef long long ll;
typedef unsigned long long ull;
      
typedef pair<int, int> PII;
typedef pair<double, int> PDI;
typedef pair<ll, ll> PLL;
typedef pair<double, double> PDD;
typedef pair<double, pair<int, double> > PDID;
typedef pair<char, char> PCC;
typedef pair<char, pair<int, int> > PCII;
typedef pair<int, pair<int, int> > PIII;
typedef pair<int, pair<int, pair<int, int> > > PIIII;
typedef pair<ll, pair<int, int> > PLII;
 
const int maxn = 1e5 + 7;
const int N = 2010 + 7;
const int M = 1e6 + 7;
const int mod = 998244353;
const int inf = 0x3f3f3f3f;
// const ll INF = 0x3f3f3f3f3f3f3f3f;
const double pi = acos(-1);
const double eps = 1e-8;
  
ll gcd(ll a,ll b) {return b==0 ? a : gcd(b,a%b);}
ll lcm(ll a,ll b) {return a*b / gcd(a,b);}
ll qmi(ll a,ll b,ll p) {ll ans = 1; while(b) { if(b & 1) ans = ans * a % p; a = a * a % p; b >>= 1; } return ans;}
int lowbit(int x) {return x & (-x);}

int n,m;
int a[maxn];

struct node
{
	int l,r;
	int len;	//区间长度
	int g;	//区间gcd
	ll nog_cnt;	//区间内gcd != g的子区间的数量
	
	vector<PII> pre,ne;	//以区间 左端点/右端点 向 右/左 扩展的gcd和数量
}tr[maxn*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;
	
	//处理nog_cnt
	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);
	
	//处理合并时产生的不合法区间数(乘法原理)(左边ne 和 右边pre)
	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;
	
	//处理pre和ne
	u.pre = l.pre;
	u.ne = r.ne;
	
	//合并时的pre
	for(int i=0;i<r.pre.size();i++)
		if(r.pre[i].fi % u.pre.back().fi == 0) 	//说明合并后gcd一定是u.pre.back().fi
			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});
	
	//合并时的ne
	for(int i=0;i<l.ne.size();i++)
		if(l.ne[i].fi % u.ne.back().fi == 0) 	//说明合并后gcd一定是u.pre.back().fi
			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;
	}
}

void solve(int t)
{
	printf("Case #%d:\n",t);
	
	ire(n);
	for(int i=1;i<=n;i++) ire(a[i]);
	
	build(1,1,n);
	
	ire(m);
	while(m--)
	{
		int l,r;
		iire(l,r);

		node no = query(1,l,r);
		
		ll ans1 = no.g;
		ll ans2 = (ll)(no.len+1) * no.len / 2 - no.nog_cnt;
		
		printf("%d %lld\n",ans1,ans2);
	}
}

int main()
{
	int t;
	ire(t);
	for(int i=1;i<=t;i++)
	{
		solve(i);
	}
	
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值