KeepCode 3 解题报告
题目来源
| ID | Origin | Title | 
|---|---|---|
| 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
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
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
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
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; }
 
                    
                     
                    
                 
                    
                
 View Code
View Code
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号