智慧树
- 解决此题有两个要点:
- 如何判断一个线性同余方程组有没有解
- 如何统计合法子序列数目
- 先看第2点:
若一个序列是合法的,则这个序列的所有子序列都是合法的
考虑对∀1≤i≤n\forall 1\leq i\leq n∀1≤i≤n,求出以iii为左端点时,序列右端点最右能取到哪,记为nxt[i]nxt[i]nxt[i]
若不考虑l,rl,rl,r的限制,所有合法子序列数=∑i=1n(nxt[i]−i+1)\sum_{i=1}^{n}(nxt[i]-i+1)∑i=1n(nxt[i]−i+1) - 加入l,rl,rl,r的限制,相当于限制了:
1.序列左端点必须≥l\geq l≥l
2.序列右端点必须≤r\leq r≤r
两个限制不好同时处理,我们考虑分开处理:
-采用离线做法,把询问按lll从大到小排序,对每个询问,只考虑 序列左端点i≥i\geqi≥当前的lll 的序列,这样便可保证满足第一个限制。
-对于第二个限制,维护cnt[j]cnt[j]cnt[j]表示 jjj是多少个 当前纳入考虑的合法序列 的右端点,则询问(l,r)(l,r)(l,r)的答案为∑j=lrcnt[j]\sum_{j=l}^{r}cnt[j]∑j=lrcnt[j]
以上可以用线段树/树状数组维护(树状数组的区间修改、区间查询戳这里)。 - 再看第1点:
- 朴素做法:
既然不保证mim_imi为质数,那么就不能用CRTCRTCRT,只能用exCRTexCRTexCRT了
考虑方程组:
{x≡b1(modm1)x≡b2(modm2)\begin{cases}x\equiv b_1(\mod m_1) \\x\equiv b_2(\mod m_2)\end{cases}{x≡b1(modm1)x≡b2(modm2)
则x=m1k1+b1=m2k2+b2x=m_1k_1+b_1=m_2k_2+b_2x=m1k1+b1=m2k2+b2
∴m1k1−m2k2=b2−b1\therefore m_1k_1-m_2k_2=b_2-b_1∴m1k1−m2k2=b2−b1
由拓展欧几里得知,k1,k2k_1,k_2k1,k2有解,当且仅当∣b2−b1∣=gcd(m1,m2)|b_2-b_1|=gcd(m_1,m_2)∣b2−b1∣=gcd(m1,m2)
若k1,k2k_1,k_2k1,k2有解,我们可以得到一个同时符合两条方程的xxx值,设为x′x'x′
那么两个方程可以合并为一个新的方程:
x≡x′(modlcm(m1,m2))x\equiv x'(\mod lcm(m_1,m_2))x≡x′(modlcm(m1,m2))
用新方程再去和其它方程合并即可(ps:合并方程求nxtnxtnxt的过程可以用ST表优化) - 正解:
当MMM较大时,由于解的模数较大,不宜基于求解来维护线性同余方程组。事实上,“求解”浪费了信息,我们只需要知道“是否有解”。
- 先考虑 mim_imi均为素数 的特殊情况:
一个方程组无解,当且仅当方程组中存在两个方程:
x≡bi(modp)x\equiv b_i(\mod p)x≡bi(modp),x≡bj(modp)x\equiv b_j(\mod p)x≡bj(modp),且bi!=bjb_i!=b_jbi!=bj
为求nxtnxtnxt,我们维护一个由线性同余方程构成的队列,支持入队、出队和查询这些方程构成的方程组加上一个新方程组是否有解。
而判断方程组有没有解,我们只需对每个素数ppp维护 目前限制xmodpx \mod pxmodp的余数是什么、对xmodpx \mod pxmodp的余数的限制有几个 即可 - 再考虑 mim_imi为任意正整数 的情况:
假设mi=∏j=1pjajm_i=\prod_{j=1}p_j^{a_j}mi=∏j=1pjaj(ppp表示素数),
则方程x≡bi(modmi)x\equiv b_i(\mod m_i)x≡bi(modmi)可以分解成:
{x≡bi(modp1a1)x≡bi(modp2a2)x≡bi(modp3a3)...\begin{cases}x\equiv b_i(\mod p_1^{a_1}) \\x\equiv b_i(\mod p_2^{a_2})\\x\equiv b_i(\mod p_3^{a_3})\\...\end{cases}⎩⎪⎪⎪⎨⎪⎪⎪⎧x≡bi(modp1a1)x≡bi(modp2a2)x≡bi(modp3a3)...
那么一个方程组无解,当且仅当方程组中存在两个方程:
x≡bi(modpa)x\equiv b_i(\mod p^a)x≡bi(modpa),x≡bj(modpa)x\equiv b_j(\mod p^a)x≡bj(modpa),且bi!=bjb_i!=b_jbi!=bj
做法类似上面,对每个pap^apa维护 目前限制xmodpax \mod p^axmodpa的余数是什么、对xmodpax \mod p^axmodpa的余数的限制有几个 即可
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e6+10;
int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;
}
int pri[N],cnt,vis[N],mn[N];
int n,m[N],b[N],q;
int lim[N],tot[N],nxt[N];
//nxt[i]记录以i为区间左端点,区间右端点最右能取到哪
struct Que{int l,r,id;friend bool operator < (Que a,Que b){return a.l<b.l;}
}que[N];
ll ans[N];
void init(){vis[0]=vis[1]=1;for(int i=2;i<=1000000;i++){if(!vis[i]){pri[++cnt]=i;mn[i]=i;}for(int j=1;j<=cnt,pri[j]*i<=1000000;j++){vis[pri[j]*i]=1;mn[i*pri[j]]=pri[j];if(i%pri[j]==0) break;}}
}
void del(int i){int tmp=m[i];while(tmp!=1){int fac=mn[tmp],pw=1;while(tmp%fac==0){tmp/=fac;pw*=fac;}for(int k=pw;k>=1;k/=fac){tot[k]--;if(!tot[k]) lim[k]=-1;}}
}
void get_nxt(){memset(lim,-1,sizeof(lim));int j=n+1;for(int i=n;i>=1;i--){int tmp=m[i];while(tmp!=1){int fac=mn[tmp],pw=1;while(tmp%fac==0){tmp/=fac;pw*=fac;}for(int k=pw;k>=1;k/=fac){if(lim[k]!=-1&&lim[k]!=b[i]%k){while(lim[k]!=-1) del(--j);}}for(int k=pw;k>=1;k/=fac){tot[k]++;if(lim[k]==-1) lim[k]=b[i]%k;}}nxt[i]=j-1;//i最右能和j-1合并 }for(int i=n-1;i>=1;i--) nxt[i]=min(nxt[i],nxt[i+1]);
}
namespace SegmentTree{ll sum[N<<2],tag[N<<2];void pushdown(int u,int l,int r){if(tag[u]){int mid=(l+r)>>1;sum[u<<1]+=tag[u]*(mid-l+1);tag[u<<1]+=tag[u];sum[u<<1|1]+=tag[u]*(r-mid);tag[u<<1|1]+=tag[u];tag[u]=0;}}void add(int u,int l,int r,int a,int b,int x){if(a<=l&&r<=b){sum[u]+=x*(r-l+1);tag[u]+=x;return;}pushdown(u,l,r);int mid=(l+r)>>1;if(a<=mid) add(u<<1,l,mid,a,b,x);if(b>mid) add(u<<1|1,mid+1,r,a,b,x);sum[u]=sum[u<<1]+sum[u<<1|1];}ll query(int u,int l,int r,int a,int b){if(a<=l&&r<=b) return sum[u];pushdown(u,l,r);int mid=(l+r)>>1;ll res=0;if(a<=mid) res+=query(u<<1,l,mid,a,b);if(b>mid) res+=query(u<<1|1,mid+1,r,a,b);return res;}
};
using namespace SegmentTree;
int main(){init();n=read();for(int i=1;i<=n;i++)m[i]=read(),b[i]=read();get_nxt();q=read();for(int i=1;i<=q;i++){que[i].l=read();que[i].r=read();que[i].id=i;}sort(que+1,que+q+1);int tmp=n+1;for(int i=q;i>=1;i--){while(tmp>1&&tmp-1>=que[i].l){tmp--;add(1,1,n,tmp,nxt[tmp],1);//以tmp~nxt[tmp]为右端点的区间都多了一个 }ans[que[i].id]=query(1,1,n,que[i].l,que[i].r);}for(int i=1;i<=q;i++) printf("%lld\n",ans[i]);return 0;
}