题意:已知序列1,2…n。存在两个操作
1,x,y,p:统计[x,y]位置区间内有多少个元素与p互素
2,x.c:将x位置上的数改成c
n<=400000,操作数m<=1000
解法:由于操作数较小,所以先处理未操作的结果,然后再考虑数字置换后结果的该变量。
未操作时的结果:统计[x,y]中有多少个元素与p互素
这是经典的容斥原理问题,见前文博客。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
#include <queue>
#define mem(a , b) memset(a , b , sizeof(a))
#define pb(a) push_back(a)
#define mp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int maxn = 400000+10;
ll n,m,t;
vector<ll> change;//改变序列
ll dp[maxn];//i点修改过的值
ll ans;
vector<ll> factor;
vector<ll> prime;
bool not_prime[maxn];
void make_prime(){
for(ll i=2;i<maxn-5;i++){
if(!not_prime[i]) prime.push_back(i);
for(ll j=0;j<prime.size()&&prime[j]*i<maxn-5;j++){
not_prime[i*prime[j]]=1;
if(!(i%prime[j])) break;
}
}
}
//分解因子复杂度:O(sqrt(n))
void divided(ll x){
factor.clear();
for(ll i=0;i<prime.size();i++){
if(prime[i]*prime[i]>x) break;
if(x%prime[i]==0){
factor.push_back(prime[i]);
while(x%prime[i]==0) x/=prime[i];
}
}
if(x!=1) factor.push_back(x);
}
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
ll sum(ll x,ll p){//[1,x]中与p互素数的和
ll len=factor.size();
ll ans=0;
for(ll i=1;i<(1<<len);i++){
ll tmp=0,tmpans=1;
for(ll j=0;(1<<j)<=i;j++){
if((1<<j)&i){
if(tmp) tmp=0; else tmp=1;
tmpans*=factor[j];
}
}
ll k=x/tmpans;
if(tmp) ans=ans+k*(k+1)*tmpans/2;//奇数次
else ans=ans-k*(k+1)*tmpans/2;//偶数次
}
return x*(x+1)/2-ans;
}
int main(){
//freopen("a.txt","r",stdin);
make_prime();
scanf("%lld",&t);
while(t--){
memset(dp,-1,sizeof(dp));
change.clear();
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=m;i++){
ll flag,x,y,p,c;
scanf("%lld",&flag);
if(flag==2){
scanf("%lld%lld",&x,&c);
if(dp[x]==-1){
change.push_back(x);
dp[x]=c;
}
else dp[x]=c;
}
else{
scanf("%lld%lld%lld",&x,&y,&p);
ans=0;
divided(p);
ans=sum(y,p)-sum(x-1,p);
for(int j=0;j<change.size();j++){
int a=change[j];
if(a>=x&&a<=y){
if(gcd(a,p)==1) ans=ans-a;
if(gcd(dp[a],p)==1) ans=ans+dp[a];
}
}
printf("%lld\n",ans);
}
}
}
}