Johnny and Grandmaster

[CodeForces - 1361B] Johnny and Grandmaster

\(n\)个数:\(p^{k_1}, p^{k_2},...,p^{k_n}\),分成互不相交的两组,这两组内的元素和分别记为\(sum_1\)\(sum_2\),求\(|sum_1 - sum_2|_{min} \% mod\)是多少?

\(1 \leq p, n, k_i \leq 1e6\)\(mod = 1e9 + 7\)

题解

容易想到的是,\(sum_1\)\(sum_2\)分别就是两个以\(p\)进制表示的整数(只是某些位置上可能重复)。先将\(n\)个数按降序排列,为了让它们的差尽量小,将最大的\(p^{k_0}\)放入\(sum_1\)\(p^{k_1}\)放入\(sum_2\),如果\(sum_1 > sum_2\),则把\(p^{k_2}\)放入\(sum_2\),如果依然小于,重复这个过程,直到\(sum_1 \leq sum_2\)

定理 \(\exists\) \(i \in N\)使得\(p^{k_0} = p^{k_1} +p^{k_2} +p^{k_3} +\cdots + p^{k_i}\)

证明:对于任意一个长度为\(n\)的上述数列,如果\(p^{k_0} > p^{k_1} + p^{k_2} + \cdots + p^{k_{n-1}}\),那么再增加 \(t\)\(p^1\),就可以使得等号成立(进制的性质),既\(i = n - 1 + t\);如果\(p^{k_0} = p^{k_1} + p^{k_2} + \cdots + p^{k_{n-1}}\),显然\(i = n - 1\);如果\(p^{k_0} < p^{k_1} + p^{k_2} + \cdots + p^{k_{n-1}}\),则一定会有一个\(i <n - 1\),使得等号成立,理由如下,假设不存在这样的\(i\),既存在某个\(j < n - 1\),有

\[\begin{align} p^{k_0} &> p^{k_1} + p^{k_2} + \cdots + p^{k_j} &(1)\\ p^{k_0} &< p^{k_1} + p^{k_2} + \cdots + p^{k_j} + p^{k_{j+1}} &(2) \end{align} \]

然而

\[\begin{align} p^{k_0} - p^{k_1} &= (p^{k_0 -k_1}-1)p^{k_1} \\ p^{k_0} - p^{k_1} - p^{k_2} &=((p^{k_0 -k_1}-1)p^{k_1 - k_2}-1)p^{k_2} \\ &\cdots \\ p^{k_0} - p^{k_1} - p^{k_2} - \cdots - p^{k_j} &= coe*p^{k_j} \\ \end{align} \]

\(coe\)代表系数;上述推导过程说明式(1)的右边再增加 \(coe(coe \geq 1)\)\(p^{k_j}\),就能使得\(p^{k_0} = p^{k_1} + p^{k_2} + \cdots + p^{k_j} + coe*p^{k_j}\),又因为按降序排列,故\(p^{k_{j+1}} \leq p^{k_j}\),所以

\[p^{k_1} + p^{k_2} + \cdots + p^{k_j} + p^{k_{j+1}} \leq p^{k_1} + p^{k_2} + \cdots + p^{k_j} + coe*p^{k_j} =p^{k_0} \]

显然这与式(2)矛盾,故假设不成立,既 \(\exists\) \(i < n - 1\),使得等号成立。

综上所述,定理成立。

那么怎么找到这些 \(i\) 呢?举个例子:\(p = 2\)\(k = \{3,2,1\}\)\(mod = 7\)

质朴的做法是先用快速幂求出来每个数,再来贪心:\(sum_1 = 2^3 \% mod = 1\)\(sum_2 = 2^2 \% mod = 4\),显然\(sum_1 < sum_2\),故\(sum_1 = 1 + 2^1 \% mod = 3\)\(|sum_1-sum_2| \% mod = |3 - 4| \% mod = 1\),稍微用笔算一下就会发现,存在更优解\(|sum_1 - sum_2|_{min} \% mod = 2\)。因为在实际例子中,\(p^{k_i}\)可能会爆\(long\) \(long\),快速幂必须要取模才能计算。而上述栗子说明:取模操作导致无法判断大小关系。

实际上,观察定理的推导过程我们会发现,可以递推计算系数\(coe\),并且如果\(coe >1e6\),说明即使把剩下的数都加入同一组,也不能使等号关系成立(既不用快速幂计算系数从而避免了取模的问题),而这部分才具有真正的贡献。当\(coe = 0\)时,说明\(current\_sum_1 = current\_sum_2\)

代码

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

int n, p;
const int mod = 1e9 + 7;

long long fast_pow(int a, int x) {
    long long ans = 1;
    while(x) {
        if (x & 1) ans = ans * a % mod;
        x >>= 1;
        a = 1ll * a * a % mod;
    }
    return ans;
}


int main()
{
    int cas;
    cin >> cas;

    vector<int> num;
    while(cas--) {
        cin >> n >> p;

        num.clear();
        for (int i = 0; i < n; i++) {
            int x;
            cin >> x;
            num.push_back(x);
        }

        if (p == 1) {
            cout << (n & 1) << endl;
            continue;
        }

        sort(num.begin(), num.end());

        long long cur_coe = 0;
        int pos = n - 1;                 // 具有真正贡献的那部分的第一个元素 

        for (int i = n - 1; i >= 0; i--) {
            if (cur_coe == 0) {         // 说明前面已经分成的两组元素和相等,
                pos = i;
                cur_coe = 1;
                continue;
            }
            int delta = num[i + 1] - num[i];

            long long tp = 1;
            for (int j = 0; j < delta; j++) {
                tp = tp * p;
                if (tp > 1000000) goto label;
            }

            cur_coe = cur_coe * tp - 1;     // 递推计算系数 coe
            if (cur_coe > 1000000) break;
        }

        label:;

        long long ans = fast_pow(p, num[pos]);
        for (int i = 0; i < pos; i++) {
            ans = (ans - fast_pow(p, num[i]) + mod) % mod;
        }

        cout << (ans + mod) % mod << endl;
    }

    return 0;
}
posted @ 2021-02-27 11:39  天之道,利而不害  阅读(58)  评论(0编辑  收藏  举报