HDU-4407 Sum(容斥定理+伪离线处理)

  前述:这周末还是一如既往的补作业加刷题,感觉自己真的得了嗜睡症,一到周末没人叫就绝对起不来,真是浪费青春的大好时光啊骂人周末做的这几个题里面,大部分是容斥定理的题目,但是就容斥定理本身来讲,这个题目才算是我真正入门的题目,为什么怎么说呢,这个专题时间挺短的,但是有的题目确实简单的很,在进入这个专题之前我一共看了三个相关的博客,一个是组合数的,两个是容斥定理的,然后基本仿照着这三个博客的说法(他的容斥定理的写法我并没有看懂)a了10道题,基本没费什么脑子,就是拐点小弯,还都是1a的,所以也一直担心被赶超,可是就同期一起做专题的人的速度看,好像他们并没有那么轻松,估计是学了题解上的那个写法,还没看懂,没法做到融会贯通。这也是我应该吸取的教训(因为大部分的博客上面都是位运算的写法,但是我并没有学,而是根据定义写了一发,一直到现在都没出现tle的情况就一直沿用了,一直到做完GCD这道题,刨根问底之后,两个写法我都算是掌握了,而且神奇的发现,其实二者在时间上没有快慢之分,只不过位运算的写法看比较简单,但相对难理解就是了)。

  题目:HDU-4407 Sum

  题目大意:就是给两种操作,第一种就是求x-y中和p互素的数的和,第二种就是改变某个数的值。

  解题思路:其实对于我这种刚刚入门的菜鸟,这种题目还是让我煞费一番心思的,因为操作的次数很少,想到了用离线处理,然后就是容斥定理求和,在这个题目开始我才把容斥定理这个思想彻底推广到数集上,达到真正的举一反三,所以说这个题目是我的入门题目。这里面离线操作的时候遇到了一个大问题,就是万一两次都操作一个数怎么办?顿时有点方,想了好几种方法,最终都发现行不通,时间复杂度不允许,因为每次进行离线操作的时候都需要memset处理。于是想到了用一个数组存他最后变成了什么,如果和变成的那个数一样就对这一次进行操作。那么为什么是伪离线操作呢,这里只用到了思想,却不完全一样,离线操作是输入的时候记录过程,输入结束后在进行操作,而我这里选择了记录过程+在线的操作。

  AC代码:

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
long long fac[1100];
long long box[1100];
long long ch1[2010];
long long visit[400010];
long long ch2[2010];
long long n,m,t,k,total;
long long fun;
long long gcd(long long a,long long b){
    return b==0?a:gcd(b,a%b);
}
void getfac(long long p){
    long long i,j;
    long long xx=p;
    total=0;
    for (i=2;i<=sqrt(p);i++){
        if (xx%i==0){
            fac[++total]=i;
            while (xx%i==0){
                xx/=i;
            }
        }
    }
    if (xx>1)fac[++total]=xx;
}
void sfind(long long x,long long y,long long z,long long val){
    long long i,j;
    for (i=x;i<=total;i++){
        box[y]=fac[i];
        if (y==z){
            long long temp=1;
            for (j=1;j<=z;j++){
                temp=temp/gcd(temp,box[j])*box[j];
            }
            long long xx=val/temp;
            fun+=temp*xx+temp*xx*(xx-1)/2;
        }
        else sfind(i+1,y+1,z,val);
    }
}
long long solve(long long x){
    long long i,j;
    long long temp=1;
    long long ans=0;
    for (i=1;i<=total;i++){
        fun=0;
        sfind(1,1,i,x);
        ans+=fun*temp;
        temp=-temp;
    }
    ans=x+x*(x-1)/2-ans;
    return ans;
}
int main(){
    long long i,j,l,r,x,y,z,zz;
    scanf("%lld",&t);
    while (t--){
        scanf("%lld%lld",&n,&m);
        memset (ch1,0,sizeof(ch1));
        for (i=1;i<=n;i++)visit[i]=i;
        for (i=1;i<=m;i++){
            scanf("%lld%lld%lld",&x,&y,&z);
            if (x==2){
                ch1[i]=y;
                cout<<y<<endl;
                ch2[i]=z;
                visit[y]=z;
            }
            else {
                scanf("%lld",&zz);
                getfac(zz);
                long long ans=solve(z)-solve(y-1);
                for (j=1;j<=i-1;j++){
                    if (ch1[j]==0)continue;
                    if (visit[ch1[j]]!=ch2[j])continue;
                    if (ch1[j]<y||ch1[j]>z)continue;
                    long long temp1=gcd(ch2[j],zz);
                    long long temp2=gcd(ch1[j],zz);
                    if (temp1<0)temp1=-temp1;
                    if (temp2<0)temp2=-temp2;
                    if (temp1==1&&temp2!=1){
                        ans+=ch2[j];
                    }
                    else if (temp1==1&&temp2==1){
                        ans=ans-ch1[j]+ch2[j];
                    }
                    else if (temp1!=1&&temp2==1){
                        ans=ans-ch1[j];
                    }
                }
                printf("%lld\n",ans);
            }
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值