HDU 4407 Sum <容斥原理>

题目链接: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 <cstdio>
#include <map>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long LL;
const int maxn = 10010;
int Prime[maxn],vis_prime[maxn],factor[maxn];
int _fac;
void init_prime()
{
    int _top = -1;
    memset(vis_prime,0,sizeof(vis_prime));
    for(int i=2;i<maxn;i++)
    {
        if(!vis_prime[i])
        {
        Prime[++_top] = i;
        for(int j = i;j<maxn;j+=i)
            vis_prime[j] = 1;
        }
    }
}
void set_factor(int p)
{
     _fac = 0;
    for(int i=0;Prime[i]*Prime[i]<=p;i++)
    {
        if(p%Prime[i])
            continue;
        while(p%Prime[i]==0)
        {
            p/=Prime[i];
        }
        factor[_fac++] = Prime[i];
    }
    if(p!=1)
        factor[_fac++] = p;
       // printf("_fac = %d\n",_fac);
}
LL ans;
LL pre_sum(int n)
{
    return (LL)(n+1)*n/2;
}
void dfs(int step,int tot,int i,int fac,int num,int n)
{
    //printf("")
    //printf("step = %d tot = %d i = %d fac = %d num = %d n = %d\n",step,tot,i,fac,num,n);
    if(tot == i)
    {
        LL temp = num * pre_sum(n/num);
        i&1? ans -= temp: ans += temp;
        return ;
    }
    if(fac-step < i-tot||num*factor[step]>n)
        return ;
    dfs(step+1,tot+1,i,fac,num*factor[step],n );
    dfs(step+1,tot,i,fac,num,n);
}
LL solve(int n)
{
    ans = pre_sum(n);
    for(int i=1;i<=_fac;i++)
    {
        dfs(0,0,i,_fac,1,n);
    }
    return ans;
}
int gcd(int a,int b)
{
    return b ? gcd(b,a%b) : a;
}
int main()
{
    int cas;
    int n,m;
    int op,x,y,p,c;
    map<int,int> mp;
    init_prime();
    scanf("%d",&cas);
    while(cas--)
    {
        mp.clear();
        scanf("%d %d",&n,&m);
        while(m--)
        {
            scanf("%d",&op);
            if(op==1)
            {
                scanf("%d %d %d",&x,&y,&p);
                set_factor(p);
                LL sum = solve(y) - solve(x-1);
                //printf("m == %d pre_sum = %lld p = %d\n",m,sum,p);
                for(map<int,int>::iterator it = mp.begin();it!=mp.end();it++)
                {
                    //cout << it->first << "   " <<it->second << endl;
                    if((*it).first>=x&&(*it).first<=y)
                    {
                        if(gcd((*it).first,p)==1)
                        sum -= (*it).first;
                       if(gcd((*it).second,p)==1)
                        sum += (*it).second;
                    }

                }

                printf("%I64d\n",sum);
            }
            else
            {
                scanf("%d %d",&x,&c);
                mp[x] = c;
            }
        }
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值