CCPC-Wannafly Winter Camp - 数论(Div. 1)- 整除理论习题

CCPC-Wannafly Winter Camp - 数论(Div. 1)- 整除理论习题

感谢 tangjz 的精彩讲解以及有趣的题单!

本文中的题意全部来自原 Slideshow。

1. Codeforces - 664A - Complicated GCD(最大公约数性质的应用)

题目链接:https://codeforces.com/contest/664/problem/A

难度: 900 900 900

I promise you all that this should be the easiest problem for most people.

1.1 题意

给定整数 a , b ( 1 ≤ a ≤ b ≤ 1 0 100 ) a,b(1 \le a \le b \le 10^{100}) a,b(1ab10100),求
gcd ⁡ ( a , a + 1 , ⋯ b ) \gcd(a,a+1,\cdots b) gcd(a,a+1,b)
的值。

1.2 解题过程

a = b a=b a=b 时,答案就是 a a a

否则,注意到相邻数字的 gcd ⁡ \gcd gcd 1 1 1,所以此时答案为 1 1 1

注意本题需要高精度读入。

时间复杂度: O ( 100 ) O(100) O(100)

2. Codeforces - 235A - LCM Challenge(最小公倍数性质)

题目链接:https://codeforces.com/contest/235/problem/A

难度: 1600 1600 1600

2.1 题意

给定整数 n ( 1 ≤ n ≤ 1 0 6 ) n(1 \le n \le 10^6) n(1n106),选出三个不超过 n n n正整数 x , y , z x,y,z x,y,z 使得 lcm ( x , y , z ) \text{lcm}(x,y,z) lcm(x,y,z) 最大。

2.2 解题过程

我们需要使用最小公倍数的两个性质:

  1. lcm ( n , n − 1 ) = n ( n − 1 ) \text{lcm}(n,n-1)=n(n-1) lcm(n,n1)=n(n1)
  2. n n n 为奇数,则 lcm ( n , n − 2 ) = n ( n − 2 ) \text{lcm}(n,n-2)=n(n-2) lcm(n,n2)=n(n2),因为若想让 gcd ⁡ ( n , n − 2 ) > 1 \gcd(n,n-2) > 1 gcd(n,n2)>1,至少要有因子 2 2 2

基于此,我们得到如下结论:

  1. n n n 为奇数,则答案就是 n ( n − 1 ) ( n − 2 ) n(n-1)(n-2) n(n1)(n2)

  2. n n n 为偶数,则显然 lcm ( n , n − 2 ) < n ( n − 2 ) \text{lcm}(n,n-2)<n(n-2) lcm(n,n2)<n(n2),我们不能构造 n ( n − 1 ) ( n − 2 ) n(n-1)(n-2) n(n1)(n2)

    如果构造 n ( n − 1 ) ( n − 3 ) n(n-1)(n-3) n(n1)(n3) 呢?这里面显然 n n n n − 1 n-1 n1 互质, n − 1 n-1 n1 n − 3 n-3 n3 互质。而 n n n n − 3 n-3 n3 不互质的条件为 3 ∣ n 3|n 3n,因此又分出两种情况:

    (1) 若 3 3 3 不能整除 n n n,则答案为 n ( n − 1 ) ( n − 3 ) n(n-1)(n-3) n(n1)(n3)

    (2) 若 3 3 3 能够整除 n n n,则答案为 ( n − 1 ) ( n − 2 ) ( n − 3 ) (n-1)(n-2)(n-3) (n1)(n2)(n3)

时间复杂度: O ( 1 ) O(1) O(1)

2.3 错误点

1 1 1 2 2 2 的情况比较特殊,需要特判。

1 1 1 的答案为 1 1 1 2 2 2 的答案为 2 2 2

3. Codeforces - 757B - Bash’s Big Day(因数和倍数)

题目链接:https://codeforces.com/contest/757/problem/B

难度: 1400 1400 1400

3.1 题意

给定 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n\le 10^5) n(1n105) 个正整数 x 1 , x 2 , ⋯   , x n ( 1 ≤ x i ≤ 1 0 5 ) x_1,x_2,\cdots,x_n(1 \le x_i \le 10^5) x1,x2,,xn(1xi105),选取尽可能多的数字,使得这些数字的最大公约数大于 1 1 1

如果无解,输出 1 1 1

3.2 解题过程

维护每个质因子的倍数个数 c n t [ x ] cnt[x] cnt[x],之后最大的 c n t [ x ] cnt[x] cnt[x] 就是答案。

3.3 代码

int s[maxn];
int cnt[maxn];
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)    scanf("%d", &s[i]);
    if(n == 1)
    {
        printf("1\n");
        return 0;
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 2; j * j <= s[i]; j++)
        {
            if(s[i] % j == 0)
            {
                cnt[j]++;
                while(s[i] % j == 0)   s[i] /= j;
            }
        }
        if(s[i] > 1)    cnt[s[i]]++;
    }
    int ans = 1;
    for(int i = 2; i <= 100000; i++) ans = max(ans, cnt[i]);
    printf("%d\n", ans);
    return 0;
}

4. Codeforces - 1034A - Enlarge GCD(因数和倍数)

题目链接:https://codeforces.com/contest/1034/problem/A

难度: 1700 1700 1700

4.1 题意

给定 n ( 1 ≤ n ≤ 3 × 1 0 5 ) n(1\le n \le 3 \times10^5) n(1n3×105) 个正整数 x 1 , x 2 , ⋯   , x n ( 1 ≤ x i ≤ 1.5 × 1 0 7 ) x_1,x_2,\cdots,x_n(1 \le x_i \le 1.5 \times 10^7) x1,x2,,xn(1xi1.5×107),删掉尽量少的数字使得所有数字的 gcd ⁡ \gcd gcd 变大,或者确定解不存在。

如果解存在,输出删掉的数字个数。

4.2 解题过程

d = gcd ⁡ ( x 1 , x 2 , ⋯   , x n ) d=\gcd(x_1,x_2,\cdots,x_n) d=gcd(x1,x2,,xn),则将所有的数字都除以 d d d 之后,问题转化为留下尽可能多的数字使得这些数字的 gcd ⁡ > 1 \gcd > 1 gcd>1

之后暴力枚举每个质数的倍数,统计每个质因子 x x x 可以整除的数字个数 c n t [ x ] cnt[x] cnt[x]

n − max ⁡ { c n t [ x ] } n-\max \left\{ cnt[x]\right\} nmax{cnt[x]} 就是答案。

不过我们要特判除以 d d d 之后全 1 1 1 的情况,这种情况下无解。

时间复杂度: O ( 1.5 ⋅ 1 0 7 log ⁡ ( 1.5 ⋅ 1 0 7 ) ⋅ log ⁡ ( 1.5 ⋅ 1 0 7 ) ) O(\frac{1.5 \cdot 10^7}{\log(1.5 \cdot 10^7)} \cdot \log(1.5 \cdot 10^7)) O(log(1.5107)1.5107log(1.5107))

4.3 错误点

没有特判特判除以 d d d 之后全 1 1 1 的情况,导致 WA11

4.4 代码

int a[maxn];
int cnt[50 * maxn], cnt2[50 * maxn];
bool isnotprime[50 * maxn];
ll prime[50 * maxn];
int tot = 0;
void linearsieve(int range)
{
    for(int i = 2; i <= range; i++)
    {
        if(!isnotprime[i]) prime[++tot] = i;
        for(int j = 1; j <= tot; j++)
        {
            if(i * prime[j] > range)    break;
            isnotprime[i * prime[j]] = true;
            if(i % prime[j] == 0)       break;
        }
    }
}
int main()
{
    int n;
    scanf("%d", &n);
    int gcd = 0;
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        gcd = __gcd(a[i], gcd);
    }
    for(int i = 1; i <= n; i++)
    {
        a[i] /= gcd;
        cnt[a[i]]++;
    }
    if(cnt[1] == n)
    {
        printf("-1\n");
        return 0;
    }
    linearsieve(15000000);
    for(int i = 1; i <= tot; i++)
    {
        for(int j = prime[i]; j <= 15000000; j += prime[i])
        {
            cnt2[prime[i]] += cnt[j];
        }
    }
    int ans = 0;
    for(int i = 2; i <= 15000000; i++)  ans = max(ans, cnt2[i]);
    printf("%d\n", n - ans);
    return 0;
}

5. Codeforces - 475D - CGCDSSQ(最大公约数性质 + 暴力)

题目链接:https://codeforces.com/contest/475/problem/D

难度: 2000 2000 2000

5.1 题意

给定长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 的序列 a 1 , a 2 , ⋯   , a n ( 1 ≤ a i ≤ 1 0 9 ) a_1,a_2,\cdots,a_n(1 \le a_i \le 10^9) a1,a2,,an(1ai109),有 q ( 1 ≤ q ≤ 3 ⋅ 1 0 5 ) q(1 \le q \le 3 \cdot 10^5) q(1q3105) 个询问,每次询问给出一个 x ( 1 ≤ x ≤ 1 0 9 ) x(1 \le x \le 10^9) x(1x109),问有多少个区间 [ l , r ] [l,r] [l,r] 满足 gcd ⁡ ( a l , a l + 1 , ⋯   , a r ) = x \gcd(a_l,a_{l+1},\cdots,a_r)=x gcd(al,al+1,,ar)=x

5.2 解题过程

首先,我们需要知道,右端点固定的所有区间中 gcd ⁡ \gcd gcd 的种类数只有 O ( log ⁡ n ) O(\log n) O(logn) 的量级。

基于这个事实,我们可以从左到右枚举右端点坐标 r r r,之后暴力地将 a r a_r ar 与以 a r − 1 a_{r-1} ar1 为右端点的所有区间取 gcd ⁡ \gcd gcd 之后统计答案,不要忘记将 a r a_r ar 自身统计到答案中。

维护的话,可以考虑开三个 unordered_map c n t cnt cnt 统计以 a r − 1 a_{r-1} ar1 为右端点的所有 gcd ⁡ \gcd gcd 的个数, c n t 2 cnt_2 cnt2 统计以 a r a_{r} ar 为右端点的所有 gcd ⁡ \gcd gcd 的个数, a n s ans ans 统计每一种 gcd ⁡ \gcd gcd 的个数。

时间复杂度: O ( n log ⁡ 1 0 9 ) O(n \log 10^9) O(nlog109)

5.3 错误点

本题的答案可以达到 long long 量级,如果开 int 的话就爆了。

5.4 代码

ll a[maxn];
unordered_map<ll, ll> cnt, cnt2;
unordered_map<ll, ll> ans;
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    for(int r = 1; r <= n; r++)
    {
        for(auto element: cnt)
        {
            ll gcd = __gcd(element.first, a[r]);
            cnt2[gcd] += element.second;
            ans[gcd] += element.second;
        }
        cnt = cnt2;
        cnt[a[r]]++;
        ans[a[r]]++;
        cnt2.clear();
    }
    int q, x;
    scanf("%d", &q);
    while(q--)
    {
        scanf("%d", &x);
        if(ans.count(x) == 0)   puts("0");
        else printf("%lld\n", ans[x]);
    }
    return 0;
}

6. Luogu - P3499 - [POI2010]NAJ-Divine Divisor( O ( n 1 3 ) O(n^\frac{1}{3}) O(n31) 枚举质因子 + 暴力)

题目链接:https://www.luogu.org/problem/P3499

难度:省选/NOI-

6.1 题意

给定 m ( 1 ≤ m ≤ 600 ) m(1 \le m \le 600) m(1m600) 个数 a 1 , a 2 , ⋯   , a m ( 2 ≤ a i ≤ 1 0 18 ) a_1,a_2,\cdots,a_m(2 \le a_i \le 10^{18}) a1,a2,,am(2ai1018),定义 n = ∏ i = 1 m a i n=\prod_{i=1}^m a_i n=i=1mai,求最大的整数 k k k 使得存在 d > 1 , d ∣ n , d k ∣ n d > 1, d|n, d^k|n d>1,dn,dkn,并对这个最大的 k k k 计算有多少可能的正整数 d d d

6.2 解题过程

一个显然的想法是,统计每一个质因子的次数(利用 unordered_map),再去计算答案。

如果通过传统方法枚举质因子的话,一定会 TLE

如果我们 O ( n 1 3 ) O(n^{\frac{1}{3}}) O(n31) 地枚举质因子,即枚举 1 0 6 10^6 106 范围内的质数,将所有数字除以这些质数到不能除为止,在此过程中统计每一种质因子的次数之和,那么最后剩下的数字只剩下了三种形式:

  1. p p p
  2. p 1 p 2 p_1 p_2 p1p2
  3. p 2 p^2 p2

我们先处理 p 2 p^2 p2,这种形式通过二分就可以判掉。

之后处理 p p p,直接 Miller-rabin 就可以。

最后处理 p 1 p 2 p_1p_2 p1p2。如果我们暴力枚举剩下的 a i a_i ai a j a_j aj 的话,会发现如果 gcd ⁡ ( a i , a j ) < min ⁡ ( a i , a j ) \gcd(a_i,a_j) < \min(a_i,a_j) gcd(ai,aj)<min(ai,aj) 的话,则 a i = p 1 p 2 a_i=p_1p_2 ai=p1p2 a j = p 2 p 3 a_j=p_2p_3 aj=p2p3,即 gcd ⁡ ( a i , a j ) = p 2 \gcd(a_i,a_j)=p_2 gcd(ai,aj)=p2,这时我们便可将 p 2 p_2 p2 的贡献加入,并将 a i a_i ai a j a_j aj 都除以 p 2 p_2 p2。进行完这波操作之后,会发现只剩下两种大于 1 1 1 的形式:一种是 p p p,跟刚才一样,直接 Miller-rabin 判断即可;还有一种是 p 1 p 2 p_1 p_2 p1p2,但是跟之前不同的是, p 1 p_1 p1 p 2 p_2 p2 都没有单独出现在某一个数字中,因此可以看成一个整体统计起来。

最后,暴力枚举 unordered_map,计算最大的 k k k,并统计其对应的质因子数量 c n t cnt cnt

对于这个 k k k,对应的 d d d 的种类数为 2 c n t − 1 2^{cnt}-1 2cnt1(考虑每个质因子的 k k k 次方选或不选,减 1 1 1 是因为 d > 1 d > 1 d>1)。

注意到这个答案会远远超过 2 64 − 1 2^{64}-1 2641,因此需要使用高精度来处理。

6.3 错误点

  1. 最终的答案没有使用高精度来处理,导致爆 long long

  2. 处理完 p 1 p 2 p_1 p_2 p1p2 之后,不要忘记还有残余的 p p p

  3. O ( m 2 ) O(m^2) O(m2) 枚举 a i a_i ai a j a_j aj 时,需要将 i = j i=j i=j 的情况跳过。

  4.  for (int i = 1; i <= m; i++) {
             if (a[i] == 1)  continue;
             if (Miller_Rabin(a[i])) {
                 mp[a[i]]++;
                 a[i] = 1;
             } else {
                 mp2[a[i]]++;
                 a[i] = 1;
             }
         }
    

    不要忘记跳过 a [ i ] = 1 a[i]=1 a[i]=1 的情况。

  5. 最终统计答案时,必须忽略质因子为 1 1 1 的情况(质因子为 1 1 1 不符合数学定义)。

6.4 代码

const int S=30;//随机算法判定次数,S越大,判错概率越小
ull mult_mod(ull a,ull b,ull c)
{
    a%=c;
    b%=c;
    ull ret=0;
    while(b)
    {
        if(b&1){ret+=a;ret%=c;}
        a<<=1;
        if(a>=c)a%=c;
        b>>=1;
    }
    return ret;
}
ull pow_mod(ull x,ull n,ull mod)
{
    if(n==1)return x%mod;
    x%=mod;
    ull tmp=x;
    ull ret=1;
    while(n)
    {
        if(n&1) ret=mult_mod(ret,tmp,mod);
        tmp=mult_mod(tmp,tmp,mod);
        n>>=1;
    }
    return ret;
}
bool check(ull a,ull n,ull x,ull t)
{
    ull ret=pow_mod(a,x,n);
    ull last=ret;
    for(ull i=1;i<=t;i++)
    {
        ret=mult_mod(ret,ret,n);
        if(ret==1&&last!=1&&last!=n-1) return true;//合数
        last=ret;
    }
    if(ret!=1) return true;
    return false;
}
bool Miller_Rabin(ull n)
{
    if(n<2)return false;
    if(n==2)return true;
    if((n&1)==0) return false;//偶数
    ull x=n-1;
    ull t=0;
    while((x&1)==0){x>>=1;t++;}
    for(int i=0;i<S;i++)
    {
        ull a=rand()%(n-1)+1;//rand()需要stdlib.h头文件
        if(check(a,n,x,t))
            return false;//合数
    }
    return true;
}
ll a[maxn], b[maxn];
bool isnotprime[maxn];
ll prime[maxn];
int cnt = 0;
void linearsieve(ll range)
{
    for(ll i = 2; i <= range; i++)
    {
        if(!isnotprime[i]) prime[++cnt] = i;
        for(int j = 1; j <= cnt; j++)
        {
            if(i * prime[j] > range)    break;
            isnotprime[i * prime[j]] = true;
            if(i % prime[j] == 0)       break;
        }
    }
}
ll find_root_2(ll x)
{
    ll left = 1, right = ll(sqrt(x)) + 55;
    while(left <= right)
    {
        ll mid = left + (right - left) / 2;
        if(mid * mid == x)  return mid;
        else if(mid * mid < x)  left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}
unordered_map<ll, int> mp, mp2;
struct bign
{
    int len, s[1005];
    bign()
    {
        memset(s, 0, sizeof(s));
        len = 1;
    }
    bign(int num)
    {
        *this = num;
    }
    bign(const char* num)
    {
        *this = num;
    }
    bign operator =(int num)   //直接以整数赋值
    {
        char s[maxn];
        sprintf(s, "%d", num);
        *this = s;
        return *this;
    }
    bign operator =(const char* num)   //以字符串赋值
    {
        len = strlen(num);
        for(int i = 0; i < len; i++)
            s[i] = num[len - i - 1] - '0';
        return *this;
    }
    string str() const   //将bign转化成字符串
    {
        string res = "";
        for(int i = 0; i < len; i++)
            res = (char) (s[i] + '0') + res;
        if(res == "")
            res = "0";
        return res;
    }
    bign operator +(const bign& b) const   //重载+号运算
    {
        bign c;
        c.len = 0;
        for(int i = 0, g = 0; g || i < max(len, b.len); i++)
        {
            int x = g;
            if(i < len) x += s[i];
            if(i < b.len) x += b.s[i];
            c.s[c.len++] = x % 10;
            g = x / 10;
        }
        return c;
    }
    void clean()   //去掉前到0
    {
        while(len > 1 && !s[len - 1])
            len--;
    }
    bign operator *(const bign& b)   //重载*号运算
    {
        bign c;
        c.len = len + b.len;
        for(int i = 0; i < len; i++)
            for(int j = 0; j < b.len; j++)
                c.s[i + j] += s[i] * b.s[j];
        for(int i = 0; i < c.len - 1; i++)
        {
            c.s[i + 1] += c.s[i] / 10;
            c.s[i] %= 10;
        }
        c.clean();
        return c;
    }
    bign operator -(const bign& b)   //重载-号运算
    {
        bign c;
        c.len = 0;
        for(int i = 0, g = 0; i < len; i++)
        {
            int x = s[i] - g;
            if(i < b.len)
                x -= b.s[i];
            if(x >= 0)
                g = 0;
            else
            {
                g = 1;
                x += 10;
            }
            c.s[c.len++] = x;
        }
        c.clean();
        return c;
    }
    bool operator <(const bign& b) const   //重载<号运算
    {
        if(len != b.len)
            return len < b.len;
        for(int i = len - 1; i >= 0; i--)
            if(s[i] != b.s[i])
                return s[i] < b.s[i];
        return false;
    }
    bool operator >(const bign& b) const   //重载>号运算
    {
        return b < *this;
    }
    bool operator <=(const bign& b)   //重载<=号运算
    {
        return !(b > *this);
    }
    bool operator ==(const bign& b)   //重载>=号运算
    {
        return !(b < *this) && !(*this < b);
    }
    bign operator +=(const bign& b)   //重载+=号运算
    {
        *this = *this + b;
        return *this;
    }
};
istream& operator >>(istream &in, bign& x)   //重载输入运算符
{
    string s;
    in >> s;
    x = s.c_str();
    return in;
}
ostream& operator <<(ostream &out, const bign& x)   //重载输出运算符
{
    out << x.str();
    return out;
}
int main()
{
    int m;
    linearsieve(1000000);
    scanf("%d", &m);
    for (int i = 1; i <= m; i++) {
        scanf("%lld", &a[i]);
    }
    memcpy(b, a, sizeof(a));
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= cnt; j++) {
            ll p = prime[j];
            if (a[i] % p)   continue;
            while (a[i] % p == 0) {
                a[i] /= p;
                mp[p]++;
            }
        }
    }
    for (int i = 1; i <= m; i++) {
        ll root = find_root_2(a[i]);
        if (root > 1) {
            mp[root] += 2;
            a[i] = 1;
        }
    }
    for (int i = 1; i <= m; i++) {
        if (a[i] == 1)  continue;
        if (Miller_Rabin(a[i])) {
            mp[a[i]]++;
            a[i] = 1;
        }
    }
    for (int i = 1; i <= m; i++) {
        if (i == 1) continue;
        for (int j = 1; j <= m; j++) {
            if (i == j) continue;
            ll gcd = __gcd(a[i], b[j]);
            if (gcd == 1 || gcd == a[i])   continue;
            mp[gcd]++;
            a[i] /= gcd;
        }
    }
    for (int i = 1; i <= m; i++) {
        if (a[i] == 1)  continue;
        if (Miller_Rabin(a[i])) {
            mp[a[i]]++;
            a[i] = 1;
        } else {
            mp2[a[i]]++;
            a[i] = 1;
        }
    }
    ll ans1 = 0, ans2 = 0;
    for (auto element: mp) {
        if (element.first == 1) continue;
        if (element.second > ans1) {
            ans1 = element.second;
            ans2 = 1;
        } else if (element.second == ans1) {
            ans2++;
        }
    }
    for (auto element: mp2) {
        if (element.first == 1) continue;
        if (element.second > ans1) {
            ans1 = element.second;
            ans2 = 2;
        } else if (element.second == ans1) {
            ans2 += 2;
        }
    }
    bign ans3 = bign(1);
    for (int i = 1; i <= ans2; i++) {
        ans3 = ans3 * 2;
    }
    ans3 = ans3 - 1;
    printf("%lld\n", ans1);
    cout << ans3 << endl;
    return 0;
}

7. Codeforces - 1033D - Divisors(因子形式分类讨论 + 暴力)

题目链接:https://codeforces.com/contest/1033/problem/D

难度: 2000 2000 2000

7.1 题意

给定 n ( 1 ≤ n ≤ 500 ) n(1 \le n \le 500) n(1n500) 个数 a 1 , a 2 , ⋯   , a n ( 1 ≤ a i ≤ 2 ⋅ 1 0 18 ) a_1,a_2,\cdots,a_n(1 \le a_i \le 2 \cdot 10^{18}) a1,a2,,an(1ai21018),定义 a = ∏ i = 1 n a i a=\prod_{i=1}^n a_i a=i=1nai,求 a a a 的约数个数。

题目保证 a i a_i ai 的约数个数在 3 3 3 5 5 5 之间。

7.2 解题过程

我们可以通过 unordered_map 存储每个质因子的幂次。

注意到,根据因子数,每个数的分解只有下面几种情况:

  1. 因子数为 3 3 3 时,可能是 p 2 p^2 p2 的形式。
  2. 因子数为 4 4 4 时,可能是 p 3 p^3 p3 或者 p 1 p 2 p_1p_2 p1p2 的形式。
  3. 因子数为 5 5 5 时,可能是 p 4 p^4 p4 的形式。

因此,我们可以先将 p 2 , p 3 , p 4 p^2,p^3,p^4 p2,p3,p4 形式的数字全部变为 1 1 1,在这个过程中将去掉的质因子和次数都存入到 unordered_map 中。

判断幂次可以通过二分实现。

之后,就只剩下了 p 1 p 2 p_1 p_2 p1p2 形式的数字。

首先进行 O ( n 2 ) O(n^2) O(n2) 的遍历, i i i 遍历之前遍历一遍的数字(设该序列为 b b b), j j j 遍历题目给定的所有数字,存在下面几种情况:

  1. b i = 1 b_i = 1 bi=1,则表明这个数字已经被处理完成,跳过即可。
  2. gcd ⁡ ( b i , a j ) = 1 \gcd(b_i,a_j)=1 gcd(bi,aj)=1,说明两个数字没有任何关系,跳过。
  3. gcd ⁡ ( b i , a j ) = b i = a j \gcd(b_i,a_j)=b_i=a_j gcd(bi,aj)=bi=aj(注意两个等号若成立则一定同时成立),暂时跳过。
  4. 其他情况, gcd ⁡ \gcd gcd b i gcd ⁡ \frac{b_i}{\gcd} gcdbi 就是我们需要统计的质因子,将它们的次数分别加 2 2 2 即可,之后将 b i b_i bi 置为 1 1 1

这时剩下的数字,可以把 p 1 p 2 p_1 p_2 p1p2 当成一个整体去统计答案,因为已经不存在存在单独的 p 1 p_1 p1 或者单独的 p 2 p_2 p2 因子的数字了。

我们可以再开一个 unordered_map 统计这种整体的幂次。

对剩下的那些数字,进行一轮与上面类似的遍历,当且仅当 gcd ⁡ ( b i , a j ) = b i = a j \gcd(b_i,a_j)=b_i=a_j gcd(bi,aj)=bi=aj 时,在新的 unordered_map 中将 gcd ⁡ \gcd gcd 对应的次数加 2 2 2

这时还会剩下一些数字,这些数字就是两两互质的了,把他们的次数各自加 1 1 1 即可。

最后遍历两个 unordered_map,统计答案即可,注意处理第二个 unordered_map 的时候需要按两个因子统计答案(即统计两次答案)。

8. Gym - 100956C - Fraction Factory( O ( n 1 3 ) O(n^\frac{1}{3}) O(n31) 枚举质因子)

题目链接:https://codeforces.com/gym/100956

题目来源:2015-2016 Petrozavodsk Winter Training Camp, SPb SU + SPb AU Contest

8.1 题意

给定 n ( 1 ≤ n ≤ 5000 ) n(1 \le n \le 5000) n(1n5000) 个整数 a 1 , a 2 , ⋯   , a n ( 1 ≤ a i ≤ 1 0 18 ) a_1,a_2,\cdots,a_n(1 \le a_i \le 10^{18}) a1,a2,,an(1ai1018) n ( 1 ≤ m ≤ 5000 ) n(1 \le m \le 5000) n(1m5000) 个整数 b 1 , b 2 , ⋯   , b n ( 1 ≤ b j ≤ 1 0 18 ) b_1,b_2,\cdots,b_n(1 \le b_j \le 10^{18}) b1,b2,,bn(1bj1018),定义 Q = a 1 ⋅ a 2 ⋯ a n b 1 ⋅ b 2 ⋯ b m = A B Q = \frac{a_1 \cdot a_2 \cdots a_n}{b_1 \cdot b_2 \cdots b_m} = \frac{A}{B} Q=b1b2bma1a2an=BA,其中 A A A B B B 互质。

现在有 k ( 1 ≤ k ≤ 50 ) k(1 \le k \le 50) k(1k50) 次查询,每次查询给出一个 M ( 2 ≤ M ≤ 1 0 18 ) M(2 \le M \le 10^{18}) M(2M1018),求一个整数 C ( 0 ≤ C < M ) C(0 \le C < M) C(0C<M) 满足 A B ≡ C ( mod  M ) \frac{A}{B} \equiv C(\text{mod }M) BAC(mod M),或者确定解不存在。

8.2 解题过程

A = a 1 ⋅ a 2 ⋯ a n A = a_1 \cdot a_2 \cdots a_n A=a1a2an B = b 1 ⋅ b 2 ⋯ b m B = b_1 \cdot b_2 \cdots b_m B=b1b2bm

如果 A A A M M M 互质,并且 B B B M M M 互质,那我们可以直接求解。

但事实是互质不一定成立,因此我们需要考虑将 M M M 分解质因数,之后将 A A A B B B M M M 的因子单独提出来,剩余的部分逆元计算之后,再将这些因子的幂次乘回去。

首先要解决的问题是:处理出 M M M 的全部因子。

根据数据范围, M M M 最大可以达到 1 0 18 10^{18} 1018,所以不可以直接分解(无视 Pollard-rho)。

考虑先枚举 1 0 6 10^6 106 内的质因子,之后会发现 M M M 只有四种形式:(1) 1 1 1 (2) p p p (3) p 2 p^2 p2 (4) p 1 p 2 p_1 p_2 p1p2

利用与 1033D 类似的思路处理,之后我们就得到了 M M M 的全部质因子。

然后,对 A A A B B B 进行试除,将所有 M M M 的质因子幂次用一个 unordered_map 统计起来。

对于剩下的 A ′ A' A B ′ B' B,计算 a n s = A ′ ⋅ i n v ( B ′ ) ans = A' \cdot inv(B') ans=Ainv(B)

计算逆元时,因为 M M M 不一定为质数,所以需要使用扩展欧几里得。

之后我们需要将刚才处理出的幂次加回来,这部分用快速幂算就可以,如果发现了负的幂次,证明无解(因为逆元不存在)。

需要注意的是,本题中的乘法必须使用 O ( 1 ) O(1) O(1) 快速乘来进行计算,否则会爆 long long

8.3 错误点

进行乘法运算时,没有使用 O ( 1 ) O(1) O(1) 快速乘,导致了爆 long long

8.4 代码

ll a[maxn], b[maxn];
bool isnotprime[maxn];
ll prime[maxn];
int cnt = 0;
unordered_map<ll, int> mp;
vector<ll> ve;
void linearsieve(int range)
{
    for(int i = 2; i <= range; i++)
    {
        if(!isnotprime[i]) prime[++cnt] = i;
        for(int j = 1; j <= cnt; j++)
        {
            if(i * prime[j] > range)    break;
            isnotprime[i * prime[j]] = true;
            if(i % prime[j] == 0)       break;
        }
    }
}
inline ll multi(ll x, ll y, ll mod)
{
    return (x*y-(ll)(((long double)x*y)/mod)*mod + mod)%mod;
}
long long ex_gcd(long long a, long long b, long long &x, long long &y, ll mod)
{
    if (b == 0)
    {
        x = 1;
        y = 0;
        return a;
    }
    else
    {
        long long r = ex_gcd(b, a % b, y, x, mod);
        y -= x * (a / b);
        return r;
    }
}
ll solve(ll a, ll b, ll c) //ax = b mod c
{
    ll x, y;
    ex_gcd(a, c, x, y, c);
    ll d = __gcd(a, c);
    //x = mod(mod(x, c) + c, c);
    x = (x % c + c) % c;
    return multi(x, (b / d), c / d);
}
ll find_root_2(ll x)
{
    ll left = 1, right = ll(sqrt(x)) + 55;
    while(left <= right)
    {
        ll mid = left + (right - left) / 2;
        if(mid * mid == x)  return mid;
        else if(mid * mid < x)  left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}
ll pow_mod(ll a, ll b, ll mod)
{
    ll res = 1;
    a = a % mod;
    while(b)
    {
        if(b & 1)  res = multi(res, a, mod);
        b = b >> 1;
        a = multi(a, a, mod);
    }
    return res;
}
int main()
{
    int n, m, q;
    linearsieve(1000000);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    for (int i = 1; i <= m; i++) scanf("%lld", &b[i]);
    scanf("%d", &q);
    while (q--) {
        ll mod;
        scanf("%lld", &mod);
        mp.clear();
        ve.clear();
        ll num = mod;
        for (int i = 1; i <= cnt; i++) {
            ll p = prime[i];
            if (num % p) continue;
            ve.pb(p);
            while (num % p == 0) num /= p;
        }
        ll root = find_root_2(num);
        if (root > 1) {
            num = 1;
            ve.pb(root);
        } else if (num > 1) {
            bool isok = false;
            for (int i = 1; i <= n; i++) {
                ll gcd = __gcd(num, a[i]);
                if (gcd == 1) continue;
                if (gcd < num) {
                    ve.pb(gcd);
                    ve.pb(num / gcd);
                    num = 1;
                    isok = true;
                    break;
                }
            }
            if (!isok) {
                for (int i = 1; i <= m; i++) {
                    ll gcd = __gcd(num, b[i]);
                    if (gcd == 1) continue;
                    if (gcd < num) {
                        ve.pb(gcd);
                        ve.pb(num / gcd);
                        num = 1;
                        isok = true;
                        break;
                    }
                }
            }
        }
        if (num > 1) ve.pb(num);
        sort(ve.begin(), ve.end());
        unique(ve.begin(), ve.end());
        ll A = 1, B = 1;
        for (int i = 1; i <= n; i++) {
            num = a[i];
            for (auto p: ve) {
                if (num % p) continue;
                while (num % p == 0) {
                    num /= p;
                    mp[p]++;
                }
            }
            A = multi(A, num, mod);
        }
        for (int i = 1; i <= m; i++) {
            num = b[i];
            for (auto p: ve) {
                if (num % p) continue;
                while (num % p == 0) {
                    num /= p;
                    mp[p]--;
                }
            }
            B = multi(B, num, mod);
        }
        ll ans = multi(A, solve(B, 1LL, mod), mod);
        bool isok = true;
        for (auto element: mp) {
            if (element.second < 0) {
                isok = false;
                break;
            }
            ans = multi(ans, pow_mod(element.first, element.second, mod), mod);
        }
        if (isok) printf("%lld\n", ans);
        else puts("DIVISION BY ZERO");
    }
    return 0;
}

9. HDU - 5447 - Good Numbers( O ( n 1 4 ) O(n^\frac{1}{4}) O(n41) 枚举质因子 + 分类讨论)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5447

9.1 题意

对于任意正整数 u u u,定义 f ( u ) f(u) f(u) u u u 的所有质因子组成的集合,如果正整数 u u u v v v 满足 u ∣ v u|v uv f ( u ) = f ( v ) f(u)=f(v) f(u)=f(v),那么认为 u u u v v v 来说是友好的。

给定两个正整数 k 1 k_1 k1 k 2 k_2 k2 ( 1 ≤ k 1 , k 2 ≤ 1 0 24 1 \le k_1, k_2 \le 10^{24} 1k1,k21024),保证 k 1 k_1 k1 k 2 k_2 k2 拥有相同的最大质因子, k 1 k_1 k1 的次大质因子不是 k 2 k_2 k2 的质因子, k 2 k_2 k2 的次大质因子不是 k 1 k_1 k1 的质因子。对于 k 1 k_1 k1 k 2 k_2 k2,分别求有多少个数对它们来说是友好的。

9.2 解题过程

对于一个数 n = p 1 k 1 p 2 k 2 ⋯ p m k m n=p_1^{k_1} p_2^{k_2} \cdots p_m^{k_m} n=p1k1p2k2pmkm,其友好数的个数为 ∏ i = 1 m k i \prod_{i=1}^m k_i i=1mki

因为这里的数字是 1 0 24 10^{24} 1024 量级的,所以不能直接分解质因数。

考虑枚举 1 0 6 10^6 106 以内的质因子,对 k 1 k_1 k1 k 2 k_2 k2 进行试除,并将质因子的幂次贡献到答案中。

处理完之后,只剩下了以下几种形式(设 p 1 > p 2 > p 3 p_1 > p_2 >p_3 p1>p2>p3):

12345678
1 1 1 p 1 p_1 p1 p 1 2 p_1^2 p12 p 1 3 p_1^3 p13 p 1 p 2 p_1p_2 p1p2 p 1 2 p 2 p_1^2 p_2 p12p2 p 1 p 2 2 p_1 p_2^2 p1p22 p 1 p 2 p 3 p_1 p_2 p_3 p1p2p3

我们只需考虑下面几种形式:

1234
p 1 2 p_1^2 p12 p 1 3 p_1^3 p13 p 1 2 p 2 p_1^2 p_2 p12p2 p 1 p 2 2 p_1 p_2^2 p1p22

保证 k 1 k_1 k1 k 2 k_2 k2 拥有相同的最大质因子, k 1 k_1 k1 的次大质因子不是 k 2 k_2 k2 的质因子, k 2 k_2 k2 的次大质因子不是 k 1 k_1 k1 的质因子。

根据这段假设,我们会发现 gcd ⁡ ( k 1 , k 2 ) \gcd(k_1,k_2) gcd(k1,k2) 仅与 p 1 p_1 p1 有关。

所以我们对 gcd ⁡ ( k 1 , k 2 ) = d \gcd(k_1,k_2)=d gcd(k1,k2)=d 进行分类讨论:

  1. d = p 1 3 d=p_1^3 d=p13 时,直接将答案乘上 3 3 3,并将 k 1 k_1 k1 k 2 k_2 k2 置为 1 1 1
  2. d = p 1 2 d=p_1^2 d=p12 p 1 p_1 p1 时,对 k 1 k_1 k1 k 2 k_2 k2 不断除以 d d d,如果 d d d 的幂次大于等于 2 2 2,则乘到答案中。

最终残余的数字中,只剩下 p 2 2 p_2^2 p22 这一种情况会对答案产生贡献,枚举之后判断即可。

判断幂次时,需要使用二分。

9.3 错误点

  1. 不可以 O ( n 1 3 ) O(n^{\frac{1}{3}}) O(n31) 地枚举质因子,这样会导致 TLE
  2. 二分时边界要设置的相对精确一些,防止爆 __int128

9.4 代码

bool isnotprime[maxn];
int prime[maxn];
int cnt = 0;
template <class T>
void read(T &x) {
	static char ch;static bool neg;
	for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
	for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
	x=neg?-x:x;
}
void linearsieve(int range)
{
    for(int i = 2; i <= range; i++)
    {
        if(!isnotprime[i]) prime[++cnt] = i;
        for(int j = 1; j <= cnt; j++)
        {
            if(i * prime[j] > range)    break;
            isnotprime[i * prime[j]] = true;
            if(i % prime[j] == 0)       break;
        }
    }
}
template <class T>
T sqrt2(T x)
{
    T root = sqrt(x);
    T left = std::max((T)0, root - 5), right = root + 5;
    while(left <= right)
    {
        T mid = (left + right) / (T)2;
        if (mid * mid == x) return mid;
        else if(mid * mid < x) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

template <class T>
T sqrt3(T x)
{
    T root = sqrt(x);
    T left = 0, right = root + 5;
    while(left <= right)
    {
       T mid = (left + right) / (T)2;
        if (mid * mid * mid == x) return mid;
        else if(mid * mid * mid < x) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}
int main()
{
    __int128 k1, k2;
    int t;
    linearsieve(1000000);
    read(t);
    while (t--) {
        read(k1); read(k2);
        ll ans1 = 1, ans2 = 1;
        for (int i = 1; i <= cnt; i++) {
            __int128 p = prime[i];
            if (k1 % p == 0) {
                ll tot = 0;
                while (k1 % p == 0) {
                    k1 /= p;
                    tot++;
                }
                ans1 *= tot;
            }
            if (k2 % p == 0) {
                ll tot = 0;
                while (k2 % p == 0) {
                    k2 /= p;
                    tot++;
                }
                ans2 *= tot;
            }
        }
        __int128 gcd = std::__gcd(k1, k2);
        __int128 root2 = sqrt2(gcd);
        __int128 root3 = sqrt3(gcd);
        if (gcd > 1) {
            if (root3 > 1) {
                ans1 *= 3;
                ans2 *= 3;
                while (k1 % root3 == 0) k1 /= root3;
                while (k2 % root3 == 0) k2 /= root3;
            } else if (root2 > 1) {
                ll tot = 0;
                while (k1 % root2 == 0) {
                    k1 /= root2;
                    tot++;
                }
                if (tot > 1)    ans1 *= tot;
                tot = 0;
                while (k2 % root2 == 0) {
                    k2 /= root2;
                    tot++;
                }
                if (tot > 1)    ans2 *= tot;
            } else {
                ll tot = 0;
                while (k1 % gcd == 0) {
                    k1 /= gcd;
                    tot++;
                }
                if (tot > 1)    ans1 *= tot;
                tot = 0;
                while (k2 % gcd == 0) {
                    k2 /= gcd;
                    tot++;
                }
                if (tot > 1)    ans2 *= tot;
            }
        }
        __int128 root2_1 = sqrt2(k1);
        __int128 root2_2 = sqrt2(k2);
        if (root2_1 > 1) {
            ans1 *= 2;
        }
        if (root2_2 > 1) {
            ans2 *= 2;
        }
        printf("%lld %lld\n", ans1, ans2);
    }
    return 0;
}

10. UVaLive - 5791 - Candy’s Candy(乱搞)

题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3802

10.1 题意

n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 种糖果,第 i i i 种糖果的数量为 C i ( 1 ≤ C i ≤ 1 0 9 ) C_i(1 \le C_i \le 10^9) Ci(1Ci109),现在要用两种方式来包装它们:

  1. 第一种方式要求每包只包含一种糖果;
  2. 第二种方式要求每包必须含所有种类糖果且每种数量均等。

此外,要求每种包装方式至少使用一次,包装后每包糖果数量相等,严格大于 1 1 1,统计合法方案数。

10.2 解题过程

设包装的尺寸为 s i z e size size,根据第二种包装方式的限制,我们知道 n ∣ s i z e n|size nsize,因此 n ∣ ∑ s i z e n | \sum size nsize,而 ∑ s i z e = ∑ i C i \sum size = \sum_i C_i size=iCi,因此有 n ∣ ∑ i C i n | \sum_i C_i niCi。因为每包糖果数量相等,因此必须满足 s i z e ∣ ∑ i C i size|\sum_i C_i sizeiCi。一个直观的做法是直接枚举 ∑ i C i ≤ 1 0 14 \sum_i C_i \le 10^{14} iCi1014 的因子,但是这样会很慢。

其实根据刚才的分析,会发现 s i z e size size 一定是 n n n 的倍数,如果我们枚举 ∑ i C i n ≤ 1 0 9 \frac{\sum_i C_i}{n} \le 10^9 niCi109 的因子,就会快很多。

对于枚举出来的因子 d d d,可计算出当前枚举的 s i z e = d ⋅ n size=d \cdot n size=dn

之后对于每一种 s i z e size size,计算出方案数后加到总答案中即可。

我们可以将所有的 C i C_i Ci 画成一个柱状图,这时我们会发现, C i C_i Ci C i + 1 C_{i + 1} Ci+1 之间的差值必须使用第一种方式来消除,因此必须满足 s i z e ∣ ∣ C i − C i + 1 ∣ size | |C_i - C_{i + 1}| sizeCiCi+1。如果考虑所有的 C i C_i Ci,会发现条件等价于 s i z e ∣ gcd ⁡ ( ∣ C 1 − C 2 ∣ , ∣ C 2 − C 3 ∣ , ⋯   , ∣ C n − 1 − C n ∣ ) size | \gcd(|C_1 - C_{2}|, |C_2 - C_{3}|, \cdots, |C_{n - 1} - C_{n}|) sizegcd(C1C2,C2C3,,Cn1Cn),如果不满足,当前答案为 0 0 0

之后,我们又发现,当我们消除上面的差值之后,所有的 C i C_i Ci 都变得一样,而第二种方式会用一种类似消消乐的方式,即在柱状图的底部以 s i z e n \frac{size}{n} nsize 行为单位消除。如果 k n \frac{k}{n} nk 不可以整除 m i n v a l u e minvalue minvalue,证明无法完全消除下面的公共部分,此时对答案的贡献为 0 0 0

对于剩余的部分,我们可以在范围内任意地选择按照第一种方式包装的个数 c n t 1 ( c n t 1 > 0 ) cnt_1(cnt_1 > 0) cnt1(cnt1>0)。所以最终最答案的贡献就是 c n t 1 cnt_1 cnt1 的方案数。计算方案数时,显然我们只需考虑 m i n v a l u e = min ⁡ { C i } minvalue = \min \left\{ C_i \right\} minvalue=min{Ci}

根据题目要求,每种包装方式至少使用一次,因此我们需要分两种情况讨论,其计算原则都是至少保留一个第二种包装(第一种包装数量为 0 0 0 的情况很显然不会被统计到答案中):

  1. k ∣ m i n v a l u e k | minvalue kminvalue,则方案数为 m i n c v a l u e k − 1 \frac{mincvalue}{k} - 1 kmincvalue1
  2. 否则方案数为 ⌊ m i n c v a l u e k ⌋ \lfloor \frac{mincvalue}{k} \rfloor kmincvalue

最终的答案为 ∑ \sum 方案数。

10.3 代码

int n;
ll gcd;
ll c[maxn];
ll get(ll k) {
    ll minvalue = 0x3f3f3f3f3f3f3f3f;
    if (k % n)  return 0;
    for (int i = 1; i <= n; i++) {
        //if (c[i] % (k / n)) return 0;
        minvalue = min(minvalue, c[i]);
    }
    if (minvalue % (k / n)) return 0;
    if (gcd % k)    return 0;
    if (minvalue % k == 0)  return minvalue / k - 1;
    else return minvalue / k;
}
int main()
{
    while (~scanf("%d", &n) && n) {
        ll sum = 0;
        gcd = 0;
        for (int i = 1; i <= n; i++) {
            scanf("%lld", &c[i]);
            sum += c[i];
            if (i > 1)  gcd = __gcd(gcd, abs(c[i] - c[i - 1]));
        }
        if (sum % n)
        {
            puts("0");
            continue;
        }
        sum /= n;
        ll ans = 0;
        for (ll i = 1; i * i <= sum; i++) {
            if (sum % i)    continue;
            if (i * 1LL * n >= 2)   ans += get(i * 1LL * n);
            if (i * i != sum && sum / i * 1LL * n >= 2)   ans += get(sum / i * 1LL * n);
        }

        printf("%lld\n", ans);
    }
    return 0;
}

11. Codeforces - 963C - Cutting Rectangle(乱搞)

题目链接:https://codeforces.com/contest/963/problem/C

难度: 2600 2600 2600

11.1 题意

对于长度为 A A A,宽度为 B B B 的矩形,在其上沿着平行矩形边界的直线切若干刀(要切就切到底),可以形成一系列小的矩形,给出 n ( 1   l e n ≤ 2 ⋅ 1 0 5 ) n(1\ le n \le 2 \cdot 10^5) n(1 len2105) 种小矩形的长 w w w、宽 h h h 和数量 c c c ( 1 ≤ w , h , c ≤ 1 0 12 1 \le w,h,c \le 10^{12} 1w,h,c1012),求有多少种 A , B A,B A,B 能够切出这些小矩形。

11.2 解题过程

d = gcd ⁡ ( c 1 , c 2 , ⋯   , c n ) d=\gcd(c_1,c_2,\cdots,c_n) d=gcd(c1,c2,,cn),则我们发现,问题可以转化为先构造一个最小的矩形,再按行按列进行复制。在这个最小的矩形中,每种小矩形的数量为 c i d \frac{c_i}{d} dci。如果这个最小的矩形能够成功构造的话,答案就是 d d d 的因子数,否则答案为 0 0 0

现在只剩下了如何 check 这个最小的矩形能否构造成功。

如果我们从行(长)优先的角度考虑,会发现只有 w w w 相同的矩形才能够放在一行,而在每一行,每一种 h h h 都必须出现过且比例相当。这便成为了判定标准,实际实现时,可以采用 unordered_mapvector 进行处理,具体处理过程可以参考代码。

时间复杂度: O ( n log ⁡ 1 0 12 + 1 0 12 ) O(n \log 10^{12} + \sqrt {10^{12}}) O(nlog1012+1012 )

unordered_map<ll, vector<pll>> mp;
ll w[maxn], h[maxn], c[maxn];
vector<pll> standard;
bool check() {
    for (auto &element: mp) {
        if (element.second.size() != standard.size()) return false;
        int n = standard.size();
        ll d = 0;
        for (int i = 0; i < n; i++) {
            d = __gcd(d, element.second[i].second);
        }
        for (int i = 0; i < n; i++) {
            element.second[i].second /= d;
            if (element.second[i] != standard[i]) return false;
        }
    }
    return true;
}
int main()
{
    int n;
    scanf("%d", &n);
    ll gcd = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%lld%lld%lld", &w[i], &h[i], &c[i]);
        mp[w[i]].push_back(pll(h[i], c[i]));
        gcd = __gcd(gcd, c[i]);
    }
    for (auto &element: mp) {
        for (auto &x: element.second) {
            x.second /= gcd;
        }
        sort(element.second.begin(), element.second.end());
    }
    standard = mp.begin()->second;
    ll d = 0;
    for (auto &x: standard) {
        d = __gcd(d, x.second);
    }
    for (auto &x: standard) {
        x.second /= d;
    }
    if (!check()) return 0 * puts("0");
    ll ans = 0;
    for (ll i = 1; i * i <= gcd; i++) {
        if (gcd % i) continue;
        ans++;
        if (i * i != gcd) ans++;
    }
    printf("%lld\n", ans);
    return 0;
}

12. Codeforces - 512B - Fox And Jumping(状压 dp)

题目链接:https://codeforces.com/contest/512/problem/B

难度: 2100 2100 2100

12.1 题意

n ( 1 ≤ n ≤ 300 ) n(1 \le n\le 300) n(1n300) 种卡片,第 i i i 种面值为 c i ( 1 ≤ c i ≤ 1 0 5 ) c_i(1 \le c_i \le 10^5) ci(1ci105),属性为 l i ( 1 ≤ l i ≤ 1 0 9 ) l_i(1 \le l_i \le 10^9) li(1li109),买进卡片 i i i 后可以在数轴上任意向左右移动 l i l_i li 步任意次,花费最小的代价使得从 x = 0 x=0 x=0 的位置可以到达所有 x ∈ Z x \in \textbf{Z} xZ 的位置,或者确定解不存在。

12.2 解题过程

根据斐蜀定理,从 x = 0 x=0 x=0 的位置可以到达所有 x ∈ Z x \in \textbf{Z} xZ 的位置等价于 gcd ⁡ ( l i ) = 1 \gcd(l_i)=1 gcd(li)=1

所以问题转化为选取总花费最小的卡片,使得这些卡片属性的最大公约数为 1 1 1

如果所有卡片属性的最大公约数大于 1 1 1 的话,表明问题无解。

注意到 2 ⋅ 3 ⋅ 5 ⋅ 7 ⋅ 11 ⋅ 13 ⋅ 17 ⋅ 19 ⋅ 23 ≤ 1 0 9 2 \cdot 3 \cdot 5 \cdot 7 \cdot 11 \cdot 13 \cdot 17 \cdot 19 \cdot 23 \le 10^9 23571113171923109 2 ⋅ 3 ⋅ 5 ⋅ 7 ⋅ 11 ⋅ 13 ⋅ 17 ⋅ 19 ⋅ 23 ⋅ 29 > 1 0 9 2 \cdot 3 \cdot 5 \cdot 7 \cdot 11 \cdot 13 \cdot 17 \cdot 19 \cdot 23 \cdot 29 > 10^9 2357111317192329>109

因此 1 0 9 10^9 109 以内数字的质因子数量最多只有 9 9 9 个。

计算以每一张卡片为起点(即这张卡片为必选的条件下),可以达到的最小花费。

因此枚举起始卡牌,之后计算其质因子集合。定义质因子状态 S S S 为一串二进制数,当前位 i i i 1 1 1 表明存在第 i i i 个质因子。

然后进行状压 dp,定义 d p [ S ] dp[S] dp[S] 为实现质因子状态为 S S S 的最小花费,则起始状态为 d p [ 11 ⋯ 1 ] dp[11\cdots 1] dp[111],目标状态为 d p [ 00 ⋯ 0 ] dp[00\cdots 0] dp[000]。在当前起点下,预处理出每个数字的质因子情况(只考虑起点的质因子),用二进制的形式存储。之后进行状压 dp,并更新答案的最小值。

时间复杂度: O ( 2 9 ⋅ n 2 ) O(2 ^ 9 \cdot n^2) O(29n2)

12.3 代码

ll l[maxn], c[maxn];
ll dp[1 << 10];
int mask[maxn];
vector<ll> ve;
int main()
{
    int n;
    scanf("%d", &n);
    ll gcd = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &l[i]);
        gcd = __gcd(gcd, l[i]);
    }
    for (int i = 1; i <= n; i++)    scanf("%lld", &c[i]);
    if (gcd > 1) {
        puts("-1");
        return 0;
    }
    ll ans = 0x3f3f3f3f3f3f3f3f;
    for (int i = 1; i <= n; i++) {
        ve.clear();
        memset(dp, 0x3f, sizeof(dp));
        memset(mask, 0, sizeof(mask));
        ll num = l[i];
        for (int j = 2; j * j <= num; j++) {
            if (num % j)    continue;
            ve.pb(j);
            while (num % j == 0)    num /= j;
        }
        if (num > 1)    ve.pb(num);
        int cnt = ve.size();
        dp[(1 << cnt) - 1] = c[i];
        for (int j = 1; j <= n; j++) {
            int bitmask = 0;
            for (int k = 0; k < cnt; k++) {
                if (l[j] % ve[k] == 0)  bitmask |= (1 << k);
            }
            mask[j] = bitmask;
        }
        for (int j = (1 << cnt) - 1; j >= 0; j--) {
            for (int k = 1; k <= n; k++) {
                if (i == k)             continue;
                dp[j & mask[k]] = min(dp[j & mask[k]], dp[j] + c[k]);
            }
        }
        ans = min(ans, dp[0]);
    }
    printf("%lld\n", ans);
    return 0;
}

13. Codeforces - 225E - Unsolvable(推导结论)

题目链接:https://codeforces.com/contest/225/problem/E

难度: 2100 2100 2100

13.1 题意

找出最小的 n ( 1 ≤ n ≤ 40 ) n(1 \le n \le 40) n(1n40) 个正整数 z z z,使得 z = ⌊ x 2 ⌋ + y + x y z = \lfloor \frac{x}{2} \rfloor + y + xy z=2x+y+xy 不存在 x x x y y y 均为正整数的解,按照 z z z 升序输出每个 ( z  mod  ( 1 0 9 + 7 ) ) (z \text{ mod } (10^9 + 7)) (z mod (109+7))

13.2 解题过程

看到式子 z = ⌊ x 2 ⌋ + y + x y z = \lfloor \frac{x}{2} \rfloor + y + xy z=2x+y+xy,要想到根据 x x x 的奇偶进行分类讨论。

  1. x = 2 k x=2k x=2k 时,
    z = k + y + k y = k + y + 2 k y 2 z + 1 = 2 k + 2 y + 4 k y + 1 = ( 2 k + 1 ) ( 2 y + 1 ) \begin{aligned} z&=k+y+ky\\ &=k+y+2ky\\ 2z+1&=2k+2y+4ky+1\\ &=(2k+1)(2y+1) \end{aligned} z2z+1=k+y+ky=k+y+2ky=2k+2y+4ky+1=(2k+1)(2y+1)

  2. x = 2 k + 1 x=2k+1 x=2k+1 时,
    z = k + y + x y = k + y + ( 2 k + 1 ) y = k + 2 ( k + 1 ) y z + 1 = k + 1 + 2 ( k + 1 ) y = ( k + 1 ) ( 2 y + 1 ) \begin{aligned} z&=k+y+xy\\ &=k+y+(2k+1)y\\ &=k+2(k+1)y\\ z+1&=k+1+2(k+1)y\\ &=(k+1)(2y+1) \end{aligned} zz+1=k+y+xy=k+y+(2k+1)y=k+2(k+1)y=k+1+2(k+1)y=(k+1)(2y+1)

观察 z + 1 z+1 z+1 的表达式,可以发现为保证 y y y 没有正整数解,必须保证 y = 0 y=0 y=0,也就是必须保证 z + 1 z+1 z+1 没有奇质因子,即 z + 1 z+1 z+1 只能含有 2 2 2 这一质因子。因此 z + 1 = 2 t z+1=2^t z+1=2t,即 z = 2 t − 1 z=2^t-1 z=2t1

z z z 代入到 2 z + 1 2z+1 2z+1 中,得 2 z + 1 = 2 t + 1 − 2 + 1 = 2 t + 1 − 1 2z+1 = 2^{t+1}-2+1=2^{t+1}-1 2z+1=2t+12+1=2t+11

观察到 2 z + 1 2z+1 2z+1 为两个奇数的乘积,为了保证其一无正整数解,必须保证 2 z + 1 2z+1 2z+1 为质数。

因此会发现 2 z + 1 2z+1 2z+1 其实是梅森素数。

f ( n ) f(n) f(n) 为第 n n n 个答案,则有

f ( n ) = z n = 2 m e r s e n n e [ n − 1 ] − 1 − 1 f(n)=z_n=2^{mersenne[n - 1] - 1}-1 f(n)=zn=2mersenne[n1]11
m e r s e n n e mersenne mersenne 数组可以通过 OEIS 得到:

ll mersenne[] = {2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941, 11213, 19937, 21701, 23209, 44497, 86243, 110503, 132049, 216091, 756839, 859433, 1257787, 1398269, 2976221, 3021377, 6972593, 13466917, 20996011, 24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609};

13.3 代码

ll mersenne[] = {2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941, 11213, 19937, 21701, 23209, 44497, 86243, 110503, 132049, 216091, 756839, 859433, 1257787, 1398269, 2976221, 3021377, 6972593, 13466917, 20996011, 24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609};
ll pow_mod(ll a, ll b)
{
    ll res = 1;
    a = a % Mod;
    while(b)
    {
        if(b & 1)  res = (res * a) % Mod;
        b = b >> 1;
        a = (a * a) % Mod;
    }
    return res;
}
int main()
{
    int n;
    scanf("%d", &n);
    printf("%lld\n", (pow_mod(2LL, mersenne[n - 1] - 1) - 1 + Mod) % Mod);
    return 0;
}

14. HDU - 5943 - Kingdom of Obsession(质数密度的应用 + 二分图匹配)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5943

题目来源:2016 年中国大学生程序设计竞赛(杭州)

14.1 题意

给出整数 n ( 1 ≤ n ≤ 1 0 9 ) n(1 \le n\le 10^9) n(1n109) s ( 0 ≤ s ≤ 1 0 9 ) s(0 \le s \le 10^9) s(0s109),将所有 x ∈ { s + 1 , s + 2 , ⋯   , s + n } x \in \left\{ s+1,s+2,\cdots,s+n \right\} x{s+1,s+2,,s+n} y ∈ { 1 , 2 , ⋯   , n } y \in \left\{ 1,2,\cdots,n \right\} y{1,2,,n} 两两配对,只有满足 y ∣ x y|x yx 的才能配对,问是否存在可行解。

14.2 解题过程

先处理 { s + 1 , s + 2 , ⋯   , s + n } \left\{ s+1,s+2,\cdots,s+n \right\} {s+1,s+2,,s+n} { 1 , 2 , ⋯   , n } \left\{ 1,2,\cdots,n \right\} {1,2,,n} 的交集,这部分可以直接配对,直接去掉即可。

之后,会发现 1 1 1 只能和 1 1 1(这种情况就是交集,已经处理过) 或质数进行配对。

因此,如果 { s + 1 , s + 2 , ⋯   , s + n } \left\{ s+1,s+2,\cdots,s+n \right\} {s+1,s+2,,s+n} 出现了超过 1 1 1 个质数,便是无解。

根据质数分布的规律,如果处理完交集之后的 $n > $ 最大质数间隔,则一定无解。

这个间隔设置到 300 300 300 就够了。

n < 300 n < 300 n<300,直接跑二分图匹配即可。

14.3 代码

int n, cnt;
int head[maxn];
bool vis[605];
int match[maxn];
struct edge
{
    int v, nxt;
} Edge[2 * maxn];
void init()
{
    for(int i = 0; i <= 2 * n; i++)
    {
        head[i] = -1;
        vis[i] = false;
        match[i] = 0;
    }
    cnt = 0;
}
void addedge(int u, int v)
{
    Edge[cnt].v = v;
    Edge[cnt].nxt = head[u];
    head[u] = cnt++;
}
bool dfs(int id)
{
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(!vis[v])
        {
            vis[v] = true;
            if(!match[v] || dfs(match[v]))
            {
                match[v] = id;
                return true;
            }
        }
    }
    return false;
}
int solve()
{
    int res = 0;
    for(int i = 1; i <= n; i++)
    {
        memset(vis, false, sizeof(vis));
        if(dfs(i))  res++;
    }
    return res;
}
int main()
{
    int t, s;
    int kase = 0;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &s);
        int ins = max(0, n - s);
        n -= ins;
        s += ins;
        if(n > 300)
        {
            printf("Case #%d: No\n", ++kase);
            continue;
        }
        init();
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1 + s; j <= n + s; j++)
            {
                if(j % i == 0)
                {
                    addedge(i, j - s + n);
                    addedge(j - s + n, i);
                }
            }
        }
        int res = solve();
        if(res == n)    printf("Case #%d: Yes\n", ++kase);
        else printf("Case #%d: No\n", ++kase);
    }
    return 0;
}

15. HDU - 5684 - zxa and lcm(未知)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5684

太难了,实在不会做了~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值