【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 }

 

 

 

 

posted @ 2013-03-14 17:21  Mr. Ant  阅读(950)  评论(0编辑  收藏  举报