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

 

 

 

 

你可能感兴趣的:(HDU)