KeepCode 3 解题报告

题目来源

IDOriginTitle
Problem A HDU 4407 Sum
Problem B POJ 1845 Sumdiv
Problem C POJ 2480 Longge's problem
Problem D POJ 1012 Joseph
Problem E POJ 1082 Calendar Game
Problem F POJ 1099 Square Ice

Problem A 

  将题目转换下, 我们 定义函数 Sum ( 1, N ) 为 区间[ 1, N ] 与 P 互质的数的和

  则 Sum( 1, Y ) - Sum( 1, X-1) 即为 区间 [ X, Y ] 与 P 互质的数的和

  再回到本题 

    N = 400000 , M = 1000

  题目中仅有两种操作, 1为统计区间与P互质数的和, 2为更改 X 位置值 为C

  如果我们只进行 1 操作, 则我们通过定义的 Sum 函数可以很轻松的解决这个问题.

  因为 操作数目 M = 1000, 相对于 N 来讲, 它是很小的, 所以我们可以考虑将 操作2 忽视,( 单独来处理对应位置的变化情况 )

  这样我们就可以简化问题, 通过 Sum 函数来解决这个问题. Sum函数的计算方法如下:

问题: 求区间 [ 1, N ] 与 P 互质的数 的和

  首先设:  A 为 区间[1,N]与P 互质的数 的和 

      B 为 区间[1,N] 所有数的和 ( 等差数列求和 B =  (1+N)*N/2 )

      C 为 区间[1,N]与P 不互质的数 的和

  那么我们知道:

    A = B(全集) - C( A的补集 )

  明显集合 A 不好求, 我们考虑从侧面来计算.  全

  集 B 等差数列求解没问题, 至于 A的补集 C 我们可以通过 容斥原理 来求解:

首先来看容斥原理的表达式:  

    ( 核心操作: 加奇减偶 )

      

  任意正整数都可以因式分解为如下形式:

           其中( p1, p2 ... pk 为质数, ei 为次数 )

  那么任意 X , 与N 不互质, 则  GCD( N, X ) > 1

  意味着它们有公共的素因子 (最大公约数为非素数,其也是由素数相乘构成 )

  定义 F( P ) 为区间 [1, N] 以 P为因子( 不仅仅是质因子) 的数的总和

  则

   

  对于 N = 400000 以内所有数,因式分解后, 最多 不同的素因子不超过10个. 我们可以通过二进制状态枚举素因子组合情况来求 F(X)

  注意: 

    对 P 进行因式分解时, 我们可以通过试除法, 仅需筛选到    就可以了, 因为之后哪怕有素因子,也只有一个. 不可能出现平方甚至更多次方.

否则会 TLE.

解题代码:

View Code
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<map>
#include<set>
#include<algorithm>
using namespace std;

typedef long long LL;
// 此出用来处理变换的操作
// map用来映射,得到最新的更新情况
set < int > S;
map < int,int > Mp;

const int N = 1010;
const int maxn = 1010;

int n, m;
int prime[maxn],size; //maxn以内素数数量
bool vis[maxn];

void GetPrime()
{    //线性筛选素数
    // 素数筛选只处理到 sqrt(400000) 能大幅度降低处理时间,但分解素因子的时候注意 大于sqrt(400000)素因子的情况 
    memset( vis, 0, sizeof(vis));    
    size = 0; vis[0] = vis[1] = true;
    for(int i = 2; i < maxn; i++)
    {
        if( !vis[i] ) prime[size++] = i;
        for(int j = 0; j < size && prime[j]*i < maxn; j++)
        {
            vis[ prime[j]*i ] = true;
            if( i%prime[j] == 0 ) break;
        }
    }
    //素数数量
    //printf("size = %d\n", size);
}   
inline LL Include( int y, int p ) //使用容斥原理,求 [1,y] 区间与 p 不互素的和
{    
    int num = 0, a[10], t = p;    
    for(int i = 0; i < size && prime[i] <= t; i++)    
    {
        if( t%prime[i] == 0 )
        {
            a[num++] = prime[i];
            while(t%prime[i] == 0) t/=prime[i]; // 因为这里少写了个0,WA了一次。
        }
        if( t == 1 ) break;
    }    
    if( t > 1 ) a[num++] = t; //分解素因子的时候注意 大于sqrt(400000)素因子的情况的处理     
    //注意,把400000以内所有数字预处理会TLE,题目最多1000此op=1,所以每次求一次还快点 

    LL res = 0; // 存储结果,注意数值溢出    
    int mask = (1<<num)-1; //二进制表示对应位置取或不取,枚举组合情况
    for(int i = 1; i <= mask; i++)
    {
        int tot = 0;
        LL d = 1;    
        for(int j = 0; j < num; j++)
        {
            if( i&(1<<j) ) 
            {    tot++; d = d*a[j]; }
        }
        // 等差数列求和计算当前素数组合在区间[1,y]内的倍数的和    
        // 区间[1,y]为d的倍数,一共有y/d个,形成一个差值为d的等差数列        
        LL nn = 1LL*y/d, a1 = d, an = a1 + 1LL*(nn-1)*d;
        LL tmp = (a1+an)*nn/2;        // 等比数列求和
        if( (tot&1) == 0 ) tmp = -tmp; //容斥 加奇减偶
        res += tmp; 
    }
    return res;
}
LL solve( int y, int p ) //计算区间[1,y]内与p互质的数的和
{
    // 互质和sum = [1,y]区间所有数和 - 与p不互质的和
    LL sum = 1LL*(1+y)*y/2 - Include( y, p );
    return sum;
}

int gcd( int a, int b )
{    return (b==0)? a : gcd(b,a%b); }

int main()
{
//    init();
    GetPrime();
    int T;
    scanf("%d", &T);
    while( T-- )
    {
        scanf("%d%d", &n,&m);
        Mp.clear();
        S.clear();
        int op, x, y, p;
        while( m-- )
        {
            scanf("%d", &op);
            if( op == 1 )
            {
                scanf("%d%d%d", &x, &y, &p);
                LL res = solve(y,p) - solve(x-1,p);
                // 再处理区间[x,y]发生变换的    
                for(set<int>::iterator it = S.begin(); it != S.end(); it++ )
                {
                    if( (*it >= x) && (*it <= y) )    
                    {
                        if( gcd( p, *it ) == 1 )  res -= *it; //若s[i]与p互质则需要减去该值    
                        int t = Mp[ *it ];
                        if( gcd(t,p) == 1 ) res += t; //若 t 与p 互质则需加该值
                    }    
                }
                printf("%lld\n", res);
            }
            else{
                scanf("%d%d", &x, &p );
                S.insert(x);
                Mp[x] = p;
            }    
        }
    }
    return 0;
}

Problem B

  任意正整数都可以因式分解为如下形式:

           其中( p1, p2 ... pk 为质数, ei 为次数 )

  定义函数 F( N ) 为 N 的因子和

  则  

  对于    

  因为 pi 为质因子, 两两互斥, ( 积性函数性质     (x,y)两两互斥 ), 所以

   

  

  经过以上分析, 对于本题, 可以得到

  

  

  

  所以我们可以通过将 A 进行因式分解后, 对素因子单独计算然后相乘 就可以了

  提示:

    1. 对于幂的计算, 使用二分快速幂即可

    2. 对于等比数列求和时, 计算   时, 对于 除以 ( p-1 ), 我们可以通过求 (p-1) 逆元来避免除法.

但是要注意 不可求逆元 的特殊情况:

      p mod 9901 = 0  时,  

      p mod 9901 = 1  时,  

    3. 素数筛选的时候, 我们可以只筛选到    即可, 用试除法分解其素因子.

解题代码

View Code
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N = 10010; //注意,为降低时间,我们筛选到sqrt(50000000)就可以了
const int mod = 9901;

int p[2000], size;
bool vis[N];
void GetPrime()
{
    memset( vis, 0, sizeof(vis));
    size = 0;
    for(int i = 2; i < N; i++)
    {
        if( !vis[i] ) p[size++] = i;
        for(int j = 0; (j<size)&&(p[j]*i<N); j++)
        {
            vis[ p[j]*i ] = true;
            if( i%p[j] == 0 ) break;
        }
    }
//    printf("size = %d\n", size);
}
LL Mul( LL a, LL b )
{//按位模拟乘法,避免溢出
    LL res = 0;
    while( b )
    {
        if( b&1 ) if( (res+=a) >= mod ) res -= mod;
        a <<= 1; if( a >= mod ) a -= mod;
        b >>= 1;
    }
    return res;
}
LL Pow( LL x, LL k )
{ //按位模拟快速幂
    if( k ==  0 ) return 1;
    LL res = Pow( x, k/2 )%mod;
    res = res*res%mod;
    if( k&1 ) res *= x;
    return res;
}
LL ExGcd( LL a, LL b, LL &x, LL &y)
{
    if(b == 0) { x = 1; y = 0; return a; } 
    LL r = ExGcd( b, a%b, x, y );
    LL t = x; x = y; y = t-a/b*y;
    return r;
}
LL Inverse( LL A )
{//求逆元
    LL x, y;
    ExGcd( A, mod, x , y );
    return ((x%mod)+mod)%mod;
}

LL Sum( LL q, LL n )
{//等比数列求和 , 注意特殊情形,不能求逆元
    if( q%mod == 0 ) return 1;
    else if( q%mod == 1 ) return n%mod;
    else
    {
        LL A = (Pow(q%mod,n) - 1 + mod)%mod, B = Inverse( q-1 );     
        LL res = A*B%mod; 
        return res;
    }
}    

LL solve( int A, int B )
{
    int num = 0, a[50], b[50], t = A;
    for(int i = 0; (i < size) && (p[i] <= t) ; i++)
    {
        if( t%p[i] == 0 )
        {
            a[num] = p[i];
            b[num] = 1;
            t /= p[i];    
            while( t%p[i] == 0 )
            {
                t /= p[i];
                b[num]++;
            }
            num++;    
        }
        if( t == 1 ) break;    
    }
    if( t > 1 ) { a[num] = t; b[num]=1; num++; }
 
    LL ans = 1;
    for(int i = 0; i < num; i++)
        ans = ans*Sum( a[i], 1LL*b[i]*B+1 )%mod;//加上p^0项,共b[i]*B+1项    
    return ans;
}
int main()
{
    GetPrime();
    int A, B;
    while( scanf("%d%d", &A, &B) != EOF)
    {
        if( (A == 0) || (A == 1) )
            printf("%d\n", A );    
        else
        {
            LL ans = solve( A, B );    
            printf("%lld\n", ans );    
        }    
    }
    return 0;
}

Problem C

  欧拉函数      表示 区间[1, N]  与N互质的数量个数

  对于 区间[ 1, N ]  其中的任意数 X, 如果有

        GCD( N, X ) = K

  则等价于  GCD(   ,   ) = 1 

  等价于 E(  )  在区间 [ 1,  ] 与  互质的数量个数

  对于本题, 枚举 N 因子, 仅需枚举 到   , 注意当 * = N 时的特殊情况处理

解题代码

View Code
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
using namespace std;

typedef long long LL;
LL eular( LL x )
{
    if( x == 0 ) return 0;
    LL res = 1, t = x;
    for(int i = 2; i <= (int)sqrt(1.*x); i++)
    {
        if( t%i == 0 )
        {
            res *= (i-1);
            t /= i;
            while( t%i == 0 ){ res *= i; t /= i; }
        }
        if(t == 1) break;    
    }
    if(t > 1) res *= (t-1);
    return res;
}

int main()
{
    int n;
    while( scanf("%d", &n ) != EOF)
    {
        LL res = eular(n) + n;
        for(int i = 2; i <= (int)sqrt(n); i++)
        {
            if( n%i == 0 )
            {
                if( i*i == n ) res += eular(i)*i;
                else
                {
                    res += eular( i )* (n/i);
                    res += eular( n/i ) * i;
                }    
            }
        }
        printf("%lld\n", res );    
    }
    return 0;
}

Problem D 

  2K个人围成一圈,分别编号为 1,2,3,...,k,k+1,...,k+k,  前 k 次需要首先 Excute 编号为[ k+1, k+k ] 的 k 个人

    看到题目后一直想用 模线性同余方程组 搞, 后面弄好了好久,发现没规律可言, 然而K又不大,只接模拟暴力出结果然后打表就OK了.

  每次 Excute 后减少一人. 

  现在假设 数值为M ,则 当 k = 6 时, 因为每次的起点s是不同的,我们假定为 第i次的地点为 si

    第一次,  s1 + M % 2k

    第二次,  s2 + M % (2k-1)

    ....

    第 k 次,  sk  + M % (k+1)

  当然, si 是对 2K 取模, 因为围成了一个圈.  枚举 M 后, 模拟求出 K 次 ExCute的情况, 然后判定是否 合理.

解题代码

View Code
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
/*
// 计算结果代码
int main()
{
    int k = 6;
    bool vis[100];
    //while( scanf("%d",&k) != EOF )
    for( k = 1; k <= 13; k++)    
    {
        for(int m = 1; ; m++)
        {
            int s = 0, C = k+k;
            memset( vis , 0, sizeof(vis) );    
            for(int i = k+k; i > k; i-- )
            {
                int r = m%i, cnt = 1;
                while( (cnt%i) != r )
                {
                    s = (s+1)%C;    
                    if( !vis[s] ) cnt++;    
                }
                vis[s] = true;    
                s = (s+1)%C;
                while( vis[s] ) s = (s+1)%C;
            }
            bool flag= true;    
            for(int i = k; i < k+k; i++)
                if( !vis[i] ) flag = false;
            if(flag) {    printf("k = %d, m = %d\n", k, m); break; }
        }
    }
    return 0;
}
*/
int ans[15] = {0,2,7,5,30,169,441,1872,7632,1740,93313,459901,1358657,2504881};
int main()
{
    int k;
    while( scanf("%d",&k) ,k )
        printf("%d\n", ans[k] );
    return 0;
}
原文地址:https://www.cnblogs.com/yefeng1627/p/2842443.html