【HDU 4135 && HDU 2841 && HDU1695】 容斥定理+数论 (难度递增三步曲)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4135
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2841
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1695
hdu 4135
题目大意: 输入一个a,b,n。 让你求a~b中有多少个数和n互素。1和任何数都互素。
解题思路:
看到题我们不可能对i属于a~b进行遍历,然后求i是否和n有公约数,有则不互素,无则互素。时间复杂度是n^2 n最大100000,TLE。
这题要用到容斥定理。
我们可以先这样想:[1,n]中有多少个数和m互素,可以转换成[1,n]中有多少个数和m不互素(假设这个值为ans),那么互素当然就为n-ans。
问题就变成了求[1,n]中有多少个数和m不互素。
假设n=12,m=30
第一步:首先求出m的因子数(m的因子数为2,3,5)。
第二步:[1,n]中 是m因子的倍数当然就不互素了。
(2,4,6,8,10,12)->n/2 6个, (3,6,9,12)->n/3 4个,(5,10)-> n/5 2个 。
心急的就可能全部相加了。莫急,有没有发现里面出现了重复的,所以我们还要减去重复的。
公式就是 n/2+n/3+n/5-n/(2*3)-n/(2*5)-n/(3*5)+n/(2*3*5).
第三步: 关键的来了。这一步有多种方法,dfs,队列数组,位运算都行,队列数组比dfs快一点。这里我只讲第二种(队列数组), 我们可以用一个队列(数组也行)存储出现的分母,我们可以令队列的第一个元素为1,让每次出现的m的因子和队列中的元素一个一个相乘再存储到队列中,最后就会发现存储的元素就是我们上面的分母了。现在的问题又变成了我们时候用加什么时候用减,这里我们只需要每次存的时候在再乘一个(-1),就可以得到我们想要的结果了。
题目说要求[a,b]中与n互素的,我们分别求出[1,b]与n互素的以及[1,a-1]与n互素的,两个相减就是答案了。
代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 #include <vector> 6 using namespace std; 7 vector<int>vt; 8 9 __int64 n, a, b, res; 10 __int64 que[1024]; 11 12 void fx() 13 { 14 vt.clear(); 15 res=n; 16 for(int i=2; i*i<=n; i++) 17 { 18 if(res%i==0) 19 { 20 vt.push_back(i); 21 while(res%i==0) 22 res/=i; 23 } 24 } 25 if(res>1) vt.push_back(res); 26 } 27 28 __int64 cal(__int64 n, __int64 t) 29 { 30 int num=0; 31 que[num++]=1; 32 for(int i=0; i<vt.size(); i++) 33 { 34 int ep=vt[i]; 35 int k=num; 36 for(int j=0; j<k; j++) 37 que[num++]=ep*que[j]*(-1); 38 } 39 __int64 sum=0; 40 for(int i=0; i<num; i++) 41 sum+=t/que[i]; 42 return sum; 43 } 44 45 int main() 46 { 47 int T, tcase=0; 48 cin >> T; 49 while(T--) 50 { 51 cin >> a >> b >> n; 52 fx(); 53 __int64 ans=cal(n,b)-cal(n,a-1); 54 printf("Case #%d: %I64d\n",++tcase,ans); 55 } 56 return 0; 57 }
hdu 2841
题目大意: N*M的格点上有树,从0,0点可以看到多少棵树。
解题思路:
经画图推敲可以发现如果A1/B1=A2/B2那么就有一棵树看不到,所以就是找出Ai/Bi有多少种。
再可以发现A/B中,如果A,B有大于1的公约数,则A=A'*D B=B'*D,那么A/B=A'/B',也就是存在另外一组数和这种相等,则问题转换成有多少对互质的数。
本题和上一题唯一的区别就是枚举i,从1-M中找与i互质的数,其中1<=i<=N。
容注意先预处理i的所有素因子,然后容斥求就可以了。
代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 #include <vector> 6 using namespace std; 7 8 const int maxn=100001; 9 int que[maxn]; 10 vector<int>vt[maxn]; 11 12 void init() 13 { 14 for(int i=0; i<maxn; i++) 15 vt[i].clear(); 16 for(int i=2; i<maxn; i++) 17 { 18 int t=i; 19 for(int j=2; j*j<=i; j++) 20 { 21 if(t%j==0) 22 { 23 vt[i].push_back(j); 24 while(t%j==0) 25 t/=j; 26 } 27 } 28 if(t>1) vt[i].push_back(t); 29 } 30 } 31 32 __int64 cal(int n, int s) 33 { 34 int num=0; 35 que[num++]=1; 36 for(int i=0; i<vt[s].size(); i++) 37 { 38 int ep=vt[s][i]; 39 if(ep>n) break; 40 int k=num; 41 for(int j=0; j<k; j++) 42 { 43 que[num++]=que[j]*ep*(-1); 44 } 45 } 46 __int64 sum=0; 47 for(int i=0; i<num; i++) 48 { 49 sum+=n/que[i]; 50 } 51 return sum; 52 } 53 54 int main() 55 { 56 int T, n, m; 57 init(); 58 cin >> T; 59 while(T--) 60 { 61 scanf("%d%d",&n,&m); 62 __int64 ans=n; 63 for(int i=2; i<=m; i++) 64 ans+=cal(n,i); 65 printf("%I64d\n",ans); 66 } 67 return 0; 68 }
hdu1695
题目大意:给你5个数a,b,c,d,k。x属于[a,b]y属于[c,d]。 问你有多少对(x,y)的公约数为k。 注意(x,y)和 (y,x)视为同一对。
解题思路:
本题用到了容斥定理+数论素数筛选法+数论欧拉函数。 不失为一个好题。
注意看清楚题目开头解释, 你可以假想a=c=1,有了这个就更简单了。 我们可以先令端点b,d分别除以k,b/=k,d/=k。
b可能大于d,为了方便求解这里我们令d大于b,如果不是则互换。这样就只需要找[1,b],[1,d]中有多少对互素的数了。
我们令i从1~d进行遍历:
1、当i<=b时,可以直接用欧拉函数求出互素的对数。
2、当i>b时,利用容斥定理求[1,b]中与i互素的对数。
这里注意特判一下k=0的情况,藐视就是没注意这里running error time 几次。
本题用容斥定理我用了两种方法,队列数组和dfs,练练手感。队列数组比dfs快一倍。
代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 #include <vector> 6 using namespace std; 7 8 const int maxn=100005; 9 int que[maxn]; 10 bool color[maxn]; 11 int f[maxn], phi[maxn]; 12 vector<int>vt[maxn]; 13 14 void Eular() //欧拉函数 15 { 16 phi[1]=1; 17 int k, num=0; 18 memset(color,false,sizeof(color)); 19 for(int i=2; i<maxn; i++) 20 { 21 if(!color[i]) 22 { 23 f[num++]=i; 24 phi[i]=i-1; 25 } 26 for(int j=0; j<num&&(k=i*f[j])<maxn; j++) 27 { 28 color[k]=true; 29 if(i%f[j]==0) 30 { 31 phi[k]=phi[i]*f[j]; break; 32 } 33 else 34 phi[k]=phi[i]*(f[j]-1); 35 } 36 } 37 } 38 39 void init() //打表存因子 40 { 41 for(int i=2; i<maxn; i++) 42 { 43 int t=i; 44 for(int j=0; f[j]*f[j]<=i; j++) 45 { 46 if(t%f[j]==0) 47 { 48 vt[i].push_back(f[j]); 49 while(t%f[j]==0) 50 t/=f[j]; 51 } 52 } 53 if(t>1) vt[i].push_back(t); 54 } 55 } 56 57 __int64 cal(int n, int s) //队列数组实现容斥定理 58 { 59 int num=0; 60 que[num++]=1; 61 for(int i=0; i<vt[s].size(); i++) 62 { 63 int ep=vt[s][i]; 64 if(ep>n) break; 65 int k=num; 66 for(int j=0; j<k; j++) 67 { 68 que[num++]=que[j]*ep*(-1); 69 } 70 } 71 __int64 sum=0; 72 for(int i=0; i<num; i++) 73 { 74 sum+=n/que[i]; 75 } 76 return sum; 77 } 78 79 /* 80 __int64 dfs(int a, int b, int cur) //dfs实现容斥定理 81 { 82 __int64 res=0, k; 83 for(int i=a; i<vt[cur].size(); i++) 84 { 85 k=b/vt[cur][i]; 86 res+=k-dfs(i+1,k,cur); 87 } 88 return res; 89 } 90 */ 91 92 int main() 93 { 94 int a, b, c, d, k, T, tcase=0; 95 Eular(); 96 init(); 97 cin >> T; 98 while(T--) 99 { 100 scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); 101 if(k==0||k>b||k>d) 102 { 103 printf("Case %d: 0\n",++tcase); continue; 104 } 105 b=b/k, d=d/k; 106 if(b>d) swap(b,d); 107 __int64 ans=0; 108 for(int i=1; i<=b; i++) 109 { 110 ans+=phi[i]; 111 } 112 for(int i=b+1; i<=d; i++) 113 { 114 ans+=cal(b,i); 115 } 116 printf("Case %d: %I64d\n",++tcase,ans); 117 } 118 return 0; 119 }