zoukankan      html  css  js  c++  java
  • hdu--2588--欧拉函数||容斥原理

    这题 其实我觉得并不是那么容易想到欧拉函数的 但又很容易让你去联想他 因为题目的条件有点感觉适合

    擦 这句话 好矛盾啊...

    有一点 很重要 这题 很容易因为背景是gcd的 让你去想 一定要用到gcd函数=-=

    一开始 我走上了歧途 还好 看了下 数据太大了..10E啊

    我们都知道 phi( x )求出的是1-X中与X互质的元素的个数

    假设 n>=x且n%x==0 那么就说明gcd(n,x)=x

    那么 如果x>=m的话 我们是否就可以得出 ans += phi(n/x)呢?

    因为 与n/x互质的元素 再*x 那么它和n的gcd就是x了

    这边 一定是互质的 不然的话gcd(n,y*x)就是>x了

    不知道 你会不会想 为什么不直接ans += n/x呢?算什么欧拉函数啊..

    一旦这样计算的话 会有很多元素被进行重复计算

    而我们利用上面的方法 就可以巧妙地使每个满足条件的元素仅仅被计算一次 因为某个元素被++的条件是gcd(n,k*x)==x 这是等式成立 而不是>=的条件下

    即使这样做了 我们还是可能会tle  要注意在for遍历的时候是 i<=n/i 这样可以减少遍历很多很多

    其实 总而言之 就是找出n的所有大于m的约数 进行ans += phi(n/m)的计算

    我这边 写了2个- 一个是开数组的 一个是不开的

    至于 数组大小 可以大概估计下

    我去计算了下 10^9 = (2*5)^9

    根据一个数可以表示成 x = a1^p1 * a2 ^p2 …………an^pn 且a1 a2 .....an都是质数

    那么它的约数个数就是(p1+1) * (p2+1) * ......*(pn+1)  因为对于每个ai的指数pi我们都有pi+1个取法 0 , 1 , 2 .....pi我们将它进行组合 就得到了刚刚的式子

    所以这边我们可以得到10^9的约数的个数是100 那么我就扩大了100倍的数组 去存 事实证明 也够了 没有出现RE 或者是数据问题?

     1 #include <iostream>
     2 using namespace std;
     3 
     4 const int size = 10010;
     5 int fact[size];
     6 void init( int n , int& cnt )
     7 {
     8     for( int i = 1 ; i<=n/i ; i++ )
     9     {
    10         if( n%i == 0 )
    11         {
    12             if(i*i==n)
    13                 fact[cnt++] = i;
    14             else
    15             {
    16                 fact[cnt++] = i;
    17                 fact[cnt++] = n/i;
    18             }
    19         }
    20     }
    21 }
    22 
    23 int euler( int n )
    24 {
    25     int ans = n;
    26     for( int i = 2 ; i<=n/i ; i++ )
    27     {
    28         if( n%i==0 )
    29         {
    30             ans = ans / i * (i-1);
    31             while( n%i==0 )
    32                 n /= i;
    33         }
    34     }
    35     if(n>1)
    36         ans = ans / n * (n-1);
    37     return ans;
    38 }
    39 
    40 int main()
    41 {
    42     cin.sync_with_stdio(false);
    43     int t , n , m , cnt , ans;
    44     cin >> t;
    45     while( t-- )
    46     {
    47         cin >> n >> m;
    48         cnt = ans = 0;
    49         init( n , cnt );
    50         for( int i = 0 ; i<cnt ; i++ )
    51         {
    52             if( fact[i]>=m )
    53             {
    54                 ans += euler( n/fact[i] );
    55             }
    56         }
    57         cout << ans << endl;
    58     }
    59     return 0;
    60 }
    View Code

    这边 同时要注意下 i * i == n这个特殊例子的判断 也一不注意就会重复计算 当然你也可以选择再-去一次phi(i)就可以了

     1 #include <iostream>
     2 #include <cmath>
     3 using namespace std;
     4 
     5 int euler( int n )
     6 {
     7     int ans = n;
     8     for( int i = 2 ; i<=n/i ; i++ )
     9     {
    10         if( n%i==0 )
    11         {
    12             ans = ans / i * (i-1);
    13             while( n%i==0 )
    14                 n /= i;
    15         }
    16     }
    17     if(n>1)
    18         ans = ans / n * (n-1);
    19     return ans;
    20 }
    21 
    22 int main()
    23 {
    24     cin.sync_with_stdio(false);
    25     int t , n , m , ans , num;
    26     double temp , val;
    27     cin >> t;
    28     while( t-- )
    29     {
    30         cin >> n >> m;
    31         ans = 0;
    32         for( int i = 1 ; i<=n/i ; i++ )
    33         {
    34             if( n%i ==0 )
    35             {
    36                 if(i>=m)
    37                     ans += euler(n/i);
    38                 if(n/i>=m)
    39                     ans += euler(i);
    40             }
    41         }
    42         temp = sqrt(n*1.0);
    43         val = temp - floor(temp);
    44         if( val==0 )
    45         {
    46             num = (int)temp;
    47             if( num>=m && num*m<=n )
    48                 ans -= euler(num);
    49         }
    50         cout << ans << endl;
    51     }
    52     return 0;
    53 }
    View Code

    对于 容斥的解法 肯定是可以的  我先再去想想 -.-

     感觉用容斥做 更加好啊...

    先说下整体的思路 我们首先要找出所有>=m的n的因子

    但这些因子 我们要进行筛选 并不是都要用到  我们所用到的因子应该是两两不能整除且尽量越小越好

    这就表明 当我们将上面所有因子求出来的时候 最好是进行一遍sort排序 因为我遍历的时候是sqrt(n)的范围内遍历 然后将因子存进数组<这边我是用了vector> 所以得到的并不一定是有序的

    这边sort花不了多少时间的 因为数据不大的 所以没关系的

    然后 以上工作全部做好之后 接下来就是利用容斥去解决问题了

    我们将每个因子它所代表元素都可以看成一个集合 即 m<= 1*x , 2*x , 3*x ....k*x <=n反正是满足这个的关系

    然后 要是不止一个集合的话 就可以当还有神马 m<=1*y,2*y,3*y,....k*y<=n然后可能会有一些特殊的元素 正好满足了i*x = j*y这样它不就是被重复统计了吗?

    这时候 我们就要将它进行筛选

    筛选的原则 我特地翻了下博客的以前文章 的确写过一篇 当时还画了个很多圈圈的图片 这边就不展开介绍了

    就提下 待会代码中使用 容斥原理 的核心代码思想

    假设有三个元素集合 x y z 那么它们可以有几种组合呢? 答案是  (1<<cnt)-1这边的cnt自然是3了 所以一共有7种 我这边罗列下 即使元素多 道理是一样的

    x  y  z  xy  xz  yz  xyz

    1-7的二进制表示分别是

    1   2   3    4      5     6     7

    1  10  11  100  101  110  111

    我们将每一位上的1分别看成有某个元素存在 这边可以不用看成特指 当然也可以按照习惯 从右往左 分别是X Y Z这都无所谓

    我们通过&操作来进行判断

    然后就是 偶数-  奇数+ 这个可以画图来验证

    这边 我们要求的是 最小公倍数 然后用n/lcm就是个数了

    至于lcm就是 lcm * gcd = a * b  

     1 #include <iostream>
     2 #include <cmath>
     3 #include <vector>
     4 using namespace std;
     5 
     6 vector<int>fact;
     7 vector<int>ve;
     8 
     9 int gcd( int a , int b )
    10 {
    11     return a%b == 0 ? b : gcd( b , a%b );
    12 }
    13 
    14 void init( int n , int m )
    15 {
    16     fact.clear();
    17     ve.clear();
    18     double temp , val;
    19     int num;
    20     for( int i = 1 ; i<n/i ; i++ )
    21     {
    22         if( n%i==0 )
    23         {
    24             if( i>=m )
    25                 ve.push_back(i);
    26             if( n/i>=m )
    27                 ve.push_back(n/i);
    28         }
    29     }
    30     temp = sqrt(n*1.0);
    31     val = temp - floor(temp);
    32     if( val==0 )
    33     {
    34         num = (int)temp;
    35         if( num*num == n && num>=m )
    36             ve.push_back(num);
    37     }
    38 }
    39 
    40 int main()
    41 {
    42     int t , n , m , size , cnt , val , ans;
    43     bool flag;
    44     cin >> t;
    45     while( t-- )
    46     {
    47         cin >> n >> m;
    48         init(n,m);
    49         sort( ve.begin() , ve.end() );//求出所有n的因子 且都是>=m的存在 
    50         size = ve.size();
    51          for( int i = 0 ; i<size; i++ )
    52          {
    53              flag = true;
    54              for( int j = 0 ; j<i ; j++ )
    55              {
    56                  if( ve[i]%ve[j]==0 )
    57                  {
    58                      flag = false;
    59                      break;
    60                  }
    61              }
    62              if(flag)
    63                  fact.push_back( ve[i] );//去除掉了整除的因子 
    64          }
    65          size = fact.size();
    66          ans = 0;
    67          for( int i = 1 ; i<(1<<size) ; i++ )
    68          {
    69              cnt = 0;
    70              val = 1;
    71              for( int j = 0 ; j<size ; j++ )
    72              {
    73                  if( i&(1<<j) )
    74                  {
    75                      cnt ++;
    76                      val = fact[j] / gcd(fact[j],val) * val;
    77                  }
    78              }
    79              if( cnt&1 )
    80              {
    81                  ans += n / val;
    82              }
    83              else
    84              {
    85                  ans -= n / val;
    86              }
    87          }
    88          cout << ans << endl;
    89     }
    90     return 0;
    91 }
    View Code

    today:

      人生最可怕的事

      一边后悔  一边生活

      即便知道 又如何?

      这是不可避免的

    today:

      好烦那....

      心里的想法太多了...

      不能逃避....

      可能明白为什么寂寞/孤独其实都是无助 恐慌的一种表现

    just follow your heart
  • 相关阅读:
    备忘录模式---行为型
    观察者模式(Observer)---行为型
    Hadoop基础
    centos执行-查看,复制,删除-命令的脚本
    权限问题
    12月centos单词
    配置集群遇到的问题
    SSH--完全分布式主机设置【克隆过安装过Hadoop的主机后】
    java随机排座位
    NewWord
  • 原文地址:https://www.cnblogs.com/radical/p/3946002.html
Copyright © 2011-2022 走看看