抱歉,很久没有更新博客了。
这几天集中刷了容斥原理的题目,于是就来写博客巩固下。容斥原理,我想大家在高中都或多或少的学过。虽然知道原理内容,但是用来解题的话,还是有点小障碍的,特别是不知道怎么写代码。
如果读者连最基本的容斥原理都不理解的话,或者理解不深入、不知道容斥原理用来解决什么问题的话,请下载这篇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;
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(){
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;