我去,这个太太太太难了…我愿称之为究极究极究极线段树区间合并!
分析一下:
这个题最最最关键的性质就是:随着区间变大,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;
}