hdu4407 Sum(容斥原理)

题意:已知序列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);
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值