容斥原理专题一

抱歉,很久没有更新博客了。

这几天集中刷了容斥原理的题目,于是就来写博客巩固下。容斥原理,我想大家在高中都或多或少的学过。虽然知道原理内容,但是用来解题的话,还是有点小障碍的,特别是不知道怎么写代码。

如果读者连最基本的容斥原理都不理解的话,或者理解不深入、不知道容斥原理用来解决什么问题的话,请下载这篇PDF详细研读,相信会有收获:http://pan.baidu.com/s/1hrISIjy 密码: varw

容斥原理的常见代码写法一般有两种:位运算法和DFS法。简单比较一下,位运算法在写法上比较简单,而且非常好理解,利用二进制位的遍历能够无遗漏地找到所有集合,但是缺点也很明显,基本没有办法剪枝,如果遇到TLE的话,请立刻改成DFS法,并进行某种剪枝操作。DFS法,正好与位运算法相反,它不是很好写而且可能需要某种程度的剪枝,但是DFS可以处理大一点的数据,速度上要比位运算法好一点。



第一题 hdu-2204

分析:首先,我们可以简单的发现从1到n的这些数中能表示成M^2的形式的数有floor(pow(n,1.0/2.0))个,能表示成M^3的形式的数有floor(pow(n,1.0/3.0))个,但是能表示成M^4的形式的数一定可以表示成(m^2)^2的形式,我们已经在M^2的那种里面算过了,所以我们可以发现,只有幂指数K为素数时,形式为M^K的个数才需要被计算,其他合数的情况是不需要计算的。但是还是出现了一个问题,那就是比如64的情况,64可以表示为8^2,也可以表示为4^3,那这样的话,M^2和M^3都算过了一次64,那么就重复了,这还仅仅是2和3的情况,如果多个素数乘在一起的情况,就分不清到底是该加还是该减。那么这就需要用到容斥原理,这里我们要求的其实是能够表示成M^2或者M^3或者M^5或者M^7或者M^11或者。。。。。。(一直到floor(pow(n,1.0/k))==1的情况为止),那么利用容斥原理就十分的好求了。

这道题精度有点难控制,需要单独写一个开高次方的函数(测试数据非常的弱,大数据估计只有1000000000000000000一个,如果你不高兴写这个函数,那么你直接特判一下10^18就行了)而且,这里有个剪枝的小技巧,当开方的数大于100时,根本不需要开方,因为开方的结果肯定是1,想一想为什么呢?

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          100+10
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps = 1e-10;
const double  pi = acos(-1.0);
const  ll    mod = 1e9+7;
const  int   inf = 0x3f3f3f3f;
const  ll    INF = (ll)1e18+300;

/*inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

//--------------------------------------------------

int tot;
bool isprime[maxn];
int prime[maxn];
void init(int n){
    for(int i=2;i<=n;i++){
        if(!isprime[i])
            prime[tot++]=i;
        for(int j=0;prime[j]*i<=n;j++){
            isprime[prime[j]*i]=true;
            if(i%prime[j]==0)break;
        }
    }
}
ll qlow(ll a,ll n){
    ll ans=1;
    while(n){
        if(n&1){
            double t=1.0*INF/ans;
            if(t<a)return -1;
            ans=ans*a;
        }
        n>>=1;
        if(a>(1ll<<31)&&n>0)return -1;
        a=a*a;
    }
    return ans;
}
ll cal(ll n,ll k){
    ll ans=(ll)pow(n,1.0/k);
    ll tmp=qlow(ans,k);
    if(tmp==n)return ans;
    if(tmp>n||tmp==-1)ans--;
    else {
        tmp=qlow(ans+1,k);
        if(tmp<=n&&tmp!=-1)ans++;
    }
    return ans;
}
ll solve(ll n){
    vector<int>p;
    for(int i=0;i<tot;i++){
        ll t=cal(n,prime[i]);
        if(t==1)break;
        p.PB(prime[i]);
    }
    ll ans=0;
    for(int i=1;i<(1ll<<p.size());i++){
        ll mult=1,num=0;
        for(int j=0;j<p.size();j++)
            if(i&(1<<j)){
                mult*=p[j];
                num++;
            }
        ll t;
        if(num>4||mult>100)t=1;
        else t=cal(n,mult);
        if(num&1)ans+=t;
        else ans-=t;
    }
    return ans;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    init(100);
    ll n;
    while(~scanf("%lld",&n)){
        if(n<=3)puts("1");
        else printf("%lld\n",solve(n));
    }
    return 0;
}



第二题 hdu-3208

分析:这道题看上去和上一道题很类似,但是解法上改变还是很多的。

首先,这道题目是从a到b,那么我们改变胰腺癌套路,不妨用solve(n)这个函数解决从1到n的情况,然后将solve(b)-solve(a-1)就从a到b的情况。再看看题意上的改变,上一题是能表示成M^K(K>1)的数的个数,这里是每个数都可以表示成M^K(K>=1)的形式,而且K作为这个数的权值加到答案中,我们可以借鉴前面的经验。最简单的办法就是枚举每个K,单独算出权值为K的时候有多少个数,这样只要乘一下就可以累加到答案中了。我们发现这次就不是按照素数来看了,而是从2到3到4到。。。一直到(floor(pow(n,1.0/K))==1)为止。那么我们怎么算出仅能表示成M^K的形式的数的个数呢。首先我们将N开K次方求出,(M)^K中的M有多少个,这些是有可能成为的,但是其中夹杂着很多不是的。怎么判断那些不是的呢,这里观察这些不是的1,2,3。。。,M,其中如果有I能够表示成P^Q(Q>1)的话,那么显然,这个就不是,因为I=P^Q,那么这个原数就是(P^Q)^K,那么其实它是可以表示成P^(QK)的,所以他的权值不是K,而是QK(Q>1)。这样的话相当于求前一道题的逆命题,那当然非常好求。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn             200+10
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps = 1e-10;
const double  pi = acos(-1.0);
const  ll    mod = 1e9+7;
const  int   inf = 0x3f3f3f3f;
const  ll    INF = (ll)1e18+300;

/*inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值