HDU 4407 Sum(12年金华网络赛-H题-容斥原理)

题目链接:Click here~~

第一道容斥原理的题目。

题意:

有一个元素为 1~n 的数列{An},有2种操作(1000次):

1、求某段区间 [a,b] 中与 p 互质的数的和。

2、将数列中某个位置元素的值改变。

解题思路:

对于操作1,解的性质满足区间减法,则我们只需要考虑如何求 [1,n] 中与 p 互质的数的和即可。

考虑到与 p 互质的数不太好解,于是可以通过先求出与 p 不互质的数的和,然后与总和作差得到。

而一个数 x 若与 p 不互质,当且仅当两者素因子的集合有交集。

设 p 的素因子是{P1,P2,…,Pk},于是与 p 不互质的数的素因子集合可以表示成 {P1} U {P2} U … U {Pk}。

那么与 p 不互质的数的集合可以表示成 W = { P1的倍数 } U { P2的倍数 } U … U { Pk的倍数 }。

其中,{ Pk的倍数 } = { Pk*1 } + { Pk*2 } + … + { Pk*Mk } ( Pk*Mk<=n && Pk*(Mk+1)>n )。

从而,ans = sum{ W }。

于是可以通过容斥原理,求得问题的解。

举个简单的例子,假如 k=2,则 ans = 【sum{ P1的倍数 } + sum{ P2的倍数 } - sum{ P1*P2的倍数 }】。其中,某个sum{ }可以通过等差公式求得。

对于操作2,由于操作比较少,我们可以保存这些操作,当遇到要求和的时候,我们遍历之前保存的操作,找到在区间中改变的值,对应修改即可。

#include <map>
#include <stdio.h>

typedef __int64 LL;

using namespace std;

#define N 650

bool np[N];

int prime[120],fac[9],F_top,p;

LL ans;

void get_prime()
{
    int top = -1;
    for(int i=2;i<N;i++)
        if(!np[i])
        {
            prime[++top] = i;
            for(int j=i+i;j<N;j+=i)
                np[j] = true;
        }
}

void Div(int n)
{
    F_top = 0;
    for(int i=0;prime[i]*prime[i]<=n;i++)
    {
        if(n % prime[i])
            continue;
        while(n % prime[i] == 0)
            n /= prime[i];
        fac[F_top++] = prime[i];
    }
    if(n != 1)
        fac[F_top++] = n;
}

LL PreSum(int n)
{
    return (LL)n*(n+1)/2;
}

void dfs(int i,int cnt,int m,int n,int num,int x)       // C(n,m).
{
    if(cnt == m)
    {
        LL sum = num * PreSum(x/num);
        m&1 ? ans -= sum : ans += sum;
        return ;
    }
    if(num*fac[i] > x || n-i < m-cnt)
        return ;
    dfs(i+1,cnt+1,m,n,num*fac[i],x);
    dfs(i+1,cnt,m,n,num,x);
}

LL sovle(int n)
{
    ans = PreSum(n);
    for(int m=1;m<=F_top;m++)
        dfs(0,0,m,F_top,1,n);
    return ans;
}

int gcd(int a,int b)
{
    return b ? gcd(b,a%b) : a;
}

int main()
{
    int z,n,Q,cmd,a,b;
    get_prime();
    scanf("%d",&z);
    map<int,int> M;
    while(z--)
    {
        M.clear();
        scanf("%d%d",&n,&Q);
        while(Q--)
        {
            scanf("%d",&cmd);
            if(cmd == 1)
            {
                scanf("%d%d%d",&a,&b,&p);
                Div(p);
                ans = sovle(b) - sovle(a-1);
                for(map<int,int>::iterator it=M.begin();it!=M.end();it++)
                    if((*it).first >= a && (*it).first <= b)
                    {
                        if(gcd((*it).first,p) == 1)
                            ans -= (*it).first;
                        if(gcd((*it).second,p) == 1)
                            ans += (*it).second;
                    }
                printf("%I64d\n",ans);
            }
            else
            {
                scanf("%d%d",&a,&b);
                M[a] = b;
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值