18.12.1 Nuist_ACM集训队数论专场ABC题解

18.12.1 Nuist_ACM集训队数论专场ABC题解

Problem A

题目:
HDU-4704
ti
Sample Input
2
Sample Output
2
Hint

  1. For N = 2, S(1) = S(2) = 1.
  2. The input file consists of multiple test cases. ==

这道题大致的意思就是给一个数N,问你将其分成1,2,3……N个数共有多少种分发。例如:4可以分成41,32,23,11,1,21,2,12,1,11,1,1,1;共8种分法。菜鸡没看懂题目,以为换了位置还算同一种。理解题意可知用排列组合的插空法计算并化简,即:S(0)+S(1)+S(2)+……+S(N)= C N 0 + C N 1 + … … + C N N C_N^0+C_N^1+……+C_N^N CN0+CN1++CNN=2N这个公式(实际是加到N-1,可是我打不出来上下两个N-1的标识,只能这样了)。那么现在的问题就是如何计算2的超大数次方取109+7的模。介于N的范围最大到10100000,long long肯定是存不下的,我的方法是用字符串一位一位取,下面给出大致解释:例如:2543=((25)10 ∗ \ast 24)10 ∗ \ast 23。所以对任意一个大数,用for循环就可以搞定,对于每次的10次方,快速幂即可。下面贴出代码:

#include  <iostream>
#include <cstdio>
#include <cstring>
#define mod 1000000007
using namespace std;
char a[1000005];//数字太大,当作字符串输进来 
long long int len,biao[]={1,2,4,8,16,32,64,128,256,512},n,ans,i,t,flag=0;//只用2^0~2^9,直接打表了 
int main()
{
    while(gets(a))
    {
        len=strlen(a);
        a[len-1]-=1;//减1
        flag=0; 
        for(i=len-1;i>=0;i--)//退位问题
        {
            a[i]=a[i]-flag;
            flag=0;
            if(a[i]<'0')
            {
                 a[i]=a[i]+10; 
                 flag=1;
            }
            else break;
      }
      ans=1;
      for(i=0;i<len;i++)//算法实现过程 
      {
          n=9;
          t=ans;
          while(n)//快速幂 
          {
              if(n&1)
              ans=ans*t%mod;//每次取模 
              t=t*t%mod;
              n>>=1;
          }
          t=biao[a[i]-'0'];
          ans=ans*t%mod;
      }
      cout<<ans<<endl;//结果 
   }
 return 0;
}

15ms通过,但看有0msAC的,上课还讲了其他的算法,但当时没听懂,到时候看看别人的题解再说

Problem B

题目:
HDU-1395
Give a number n, find the minimum x(x>0) that satisfies 2^x mod n = 1
Input
One positive integer on each line, the value of n.
Output
If the minimum x exists, print a line with 2^x mod n = 1.
Print 2^? mod n = 1 otherwise.
You should replace x and n with specific numbers.
Sample input
2
5
Sample Output
2^? mod 2 = 1
2^4 mod 5 = 1

思路: 这道题很明显能看出当n为偶数或1的时候是无解的,由欧拉定理可知奇数必有解 菜鸡看不懂就只能直接上链接不详细解释(其实了解就好了) 由于测试数据的设置,这题可以直接暴搜快速幂,但题目好坑,输出的句子中有几个空格,不认真看直接PE。下面直接贴代码:

#include <iostream>
#include <cstdio>
using namespace std;
int main()
{	
    int n,i,x,t,j;
    while(cin>>n)
    {
        if(n%2==0||n==1)//偶数和1显然去除 
        printf("2^? mod %d = 1\n",n);//格式,有有有空格 
        else
        {
            for(i=2;;i++) //暴搜 
            {
                x=i-1;
                j=2;
                t=2;
                while(x)//快速幂 
                {
                    if(x&1)
                        t=t*j%n;
                    j=j*j%n;
                    x>>=1; 
                }
                    if(t==1)
                        break;
            }
            printf("2^%d mod %d = 1\n",i,n);//空格空格空格
        }
    } 
    return 0;
}

前面说到欧拉定理,其实这题可以它来对对暴搜的数据进行一次简化,来优化时间。由欧拉定理可知1~n-1中与n互质的数的个数m可使am ≡ \equiv 1(mod n),但m不一定是最小值,所以对m中所有不与m互质的数进行搜索,找到即可。(原理解释:设p*q==m,且am ≡ \equiv 1(mod n);则可能有ap ≡ \equiv 1(mod n)aq ≡ \equiv 1(mod n),所以只要找出m中所有不与m互质的数(即那些pq)并对其进行搜索就好了,下面是优化后的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long 
using namespace std;
LL t,e[1000];
LL mod;
LL euler_phi(LL n)//欧拉函数,找到可能实现的最大值 
{
    LL m=sqrt(n+0.5);
    LL ans=n,i;
    for(i=2;i<=m;i++)       
    if(n%i==0)        
    {      
        ans=ans/i*(i-1);
        while(n%i==0)
            n=n/i;
    }                 
    if(n>1)ans=ans/n*(n-1);
        return ans;
}
void find(LL n)//找到与m不互质的数,这些是可能的解 
{    
    LL i;
    e[t++]=n;
    for(i=2;i*i<=n;i++)        
        if(n%i==0)                    
            if(i*i==n)                
                e[t++]=i;            
            else            
            {                
                e[t++]=i;                
                e[t++]=n/i;            
            }            
}
LL pows(LL a,LL b)//快速幂后取模 
{    
    LL s=1;    
    while(b)    
    {        
        if(b&1)            
            s=(s*a)%mod;
        a=(a*a)%mod;
        b>>=1;   
    }    
    return s;
}
int main()
{    
    LL n;    	
    while(cin>>n)
    {        
        if(n%2==0||n==1)
            printf("2^? mod %d = 1\n",n);
        else        
        {            
            LL m,ans,i,s=2;            
            m=euler_phi(n);
            t=0; 
            find(m);
            sort(e,e+t);//要从小到大有序地找最小 
            mod=n;            
            for(i=0;i<t;i++)                           
                if(pows(2,e[i])==1)                
                {                    
                    ans=e[i];                    
                    break;                
                }                     
            printf("2^%d mod %d = 1\n",ans,n);       
        }    
    }    
    return 0;
}

优化之后,耗时直接从405ms降到了31ms,效果还是很显著的。
总感觉上课还讲了其他的解法

Problem C

题目:
HDU-5750
A positive proper divisor is a positive divisor of a number n, excluding n itself. For example, 1,2, and 3 are positive proper divisors of 6, but 6 itself is not.
Peter has two positive integers n and d. He would like to know the number of integers below n whose maximum positive proper divisor is d.
Input
There are multiple test cases. The first line of input contains an integer T (1≤T≤106)(1≤T≤106), indicating the number of test cases. For each test case:
The first line contains two integers n and d (2≤n,d≤109)(2≤n,d≤109).
Output
For each test case, output an integer denoting the answer.
Sample Inout
9
10 2
10 3
10 4
10 5
10 6
10 7
10 8
10 9
100 13
Sample Output
1
2
1
0
0
0
0
0
4

这题的大致意思是要找到比n小且最大因数为d的数的个数,转换思想理解,就是当d为素数时,找比d小且乘d小于n的素数的个数;当d不为素数时,求乘d小于n且小于等于d的最小素因数的素数的个数(关于为什么小于等于最小素因数,例:n=17430,d=385,d=5 ∗ \ast 7 ∗ \ast 11 ,当选到7时,找到的数为5 ∗ \ast 7 ∗ \ast 7 ∗ \ast 11=2695,而2695的最大因数为539=7 ∗ \ast 7 ∗ \ast 11,此时不满足,结束)。那么,现在所要做的问题是:1.找出素数2.判断中止。为防止TLE,找素数可以(最好)用欧拉筛法(没找到百度百科的链接)来筛选素数(同时,因筛时只要找d的素因数,打表打到34000就差不多了),将埃氏筛的O(nlogn)的时间复杂度降到O(n),具体实现在代码下说;中止判断条件由题意解析可知,以下为AC代码:

#include <iostream>
#define MAXN 34000
#define ll long long 
using namespace std;
int num,prime[MAXN];
bool vis[MAXN];
void isprime()//欧拉筛法 
{  
    ll i,j;
    num=0;
    for(i=2;i<=MAXN;++i) 
    {
        if(!vis[i])
        prime[num++]=i;
        for(j=0;j<=num&&i*prime[j]<=MAXN;++j)
        {
            vis[i*prime[j]]=true;
            if(i%prime[j]==0)break;//欧拉筛法缩时关键 
        } 
   }
} 
int main()
{
    int ans,i,N;
    ll n,d;
    isprime();//直接打表 
    std::ios::sync_with_stdio(false);//摆脱cin为保持与stdio同步而造成的TLE 
    cin>>N;
    while(N--)
    {
        cin>>n>>d;
        ans=0;
        for(i=0;i<num;++i)
        {
            if(prime[i]>d||prime[i]*d>=n)//d为素数时的中止条件 
                break;
            if(d%prime[i]==0)//d为非素数时的中止条件 
            {
                ++ans;
                break;
            }
            ++ans;
        }
        cout<<ans<<endl;
    }
    return 0;
}

悲惨地被c++的cin同步问题卡成TLE,加std::ios::sync_with_stdio(false);关闭同步或者直接用scanf和printf就好
1887ms过的,看着那些三位数的羡慕
关于欧拉筛的缩时问题,很多人会对if(i%prime[j]==0)break; 这句话不理解,以下为解释:
欧拉筛法不是用i的倍数来消去合数,而是把prime里面纪录的素数,升序来当做要消去合数的最小素因子。当 iprime[j] 的倍数时,i=k ∗ \ast prime[j],如果继续运算 j+1,i ∗ \ast prime[j+1] = prime[j] ∗ \ast k ∗ \ast prime[j+1],这里prime[j] 是最小的素因子,当i = k ∗ \ast prime[j+1] 时会重复筛一遍,所以才跳出循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值