E. Johnny and Grandmaster

E. Johnny and Grandmaster

题意:

​ 给定n个数\(P=\{p^{k_i} \}\),将这n个数划分到两个集合中,使得这两个集合的差值最小,求最小的差值对1e9+7取模。

思路:

​ 将n个数划分到两个集合可以转化为指定这n个数的正负性使得这n个数的和最小。

\(p^{k_i}\)可以看成是\(p\)进制表示的第\(k_i\)位为1,其余位为0的数,例如\(p=10,k=3\)\(p^{k_i}=1000_{10}\)。按照这种表示方法可以想到,将这n个数按照从大到小的顺序排列,如果第一位取正,那么一定存在一个\(i\),使得第2到第\(i\)全部取负时,前\(i\)位的和位大于等于0,且\(i\)可以取得尽可能的大。意思就是,如果前\(i\)位的和大于零,且\(i<n\),那么\(i\)还可以扩大。

​ 例如,第一位为\(10^3\),第二位为\(10^2\),第三位为\(10^2\)....第\(j\)位为\(10\),这种情况,模拟一下就可以明白了。

\[\\p^{k_1}:1000_{p}\\p^{k_2}:100_{p}\\p^{k_3}:100_{p}\\p^{k_4}:100_{p}\\... \]

​ 更加普遍的规律,将这n个数按照从大到小排列后,如果第\(i\)位取正,那么一定存在一个\(j\),使得\(i+1\)\(j\)全部取负,然后第\(i\)\(j\)位的和大于等于0,且\(j\)可以取得尽可能的大。

​ 按照这种思路,很容易得出一个贪心策略,第一位取正,然后后面取负,直至和为0,然后再取一个正,后面的再取负,如此循环直至n个数全部用完,最后的和即为最小的。

​ 那么怎么证明呢?数学归纳法。

​ 只有一个数的时候,这种策略一定是正确的。

​ 假设有n个数的时候这种策略也是正确的。然后当有n+1个数的时候,使用这种策略给n+1个数指定正负,得出前n个数的和为\(sum_n\),记第n+1个数为\(arr_n\),如果\(sum_n==0\),则按照这种策略求得和为\(sum_{n+1}=arr_n\),如果\(sum_{n}>0\),则按照这种策略求得和为\(sum_{n+1}=sum_n-arr_n\)

​ 假设存在一种最优策略求得和为\(best_{n+1}<sum_{n+1}\),记这种更优策略求得前n个数的和为\(best_{n}\)(如果\(best_n<0\),那么可以把最优策略规定的正负性取反得到等价策略,且\(best_n\ge0\)),根具数学归纳法的假设可以知到\(sum_n\le best_n\),即应对前n个数的时候贪心策略不比"最优策略“孬。又因为p进制数的缘故(重点),\(best_n>=arr_{n+1}\),所以最优策略在面对第n+1个数的时候会做和贪心策略等价的选择,这也就说明最优策略不会得到优于贪心策略的答案,即所谓的最优策略就是贪心策略。

​ 到这里我们已经知道为这n个数指定正负性的贪心策略,然后就是实现。

实现

​ 如果我们可以很简单的表示指数级的数,那么这题的实现就会很简单,但问题是我们表示不了,指数级的数不光会爆long long,且运算也耗时。那么该怎么办呢,想到题目中要求答案取模就可以了,那么我们是不是可以根据取模的性质在运算过程中实时的取模呢?

​ 答案并非那么简单。因为我们需要根据前面元素的和来决定当前位置的正负性,如果前面元素的和大于零,则当前元素取负,如果前面元素的和为零,则当前元素取正,然后修改这个和。这就产生了一个问题,取模会使得一个数变小,使得本来后面t个元素都负号,现在只能使一两个元素取负然后自身就变为零。

​ 那么该怎么办呢?注意到取模是对1e9+7取模,也就是只有我们的和大于这个值的时候才会真正的执行取模,而指数k的取值范围只有1e6那么点,那么我们可以从指数k入手。当算到当前元素的时候,实时计算前缀和是当前元素的多少!!倍!!,然后如果这个倍数需要取模大于1e9的时候,从此往后我们就无需判断是否为零,直接全部取负,因为后面最多1e6元素,而前缀和已经是其中最大值的1e9倍,那还判断什么呢。

tips

​ 与其说前缀和和当前元素的大小关系,不如说前缀和是当前元素的多少倍,从倍数这一概念入手可以更好的理解这题,且更符合进制这一概念。

垃圾代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=1e6+10;
const ll MOD=1e9+7;
ll qpow(ll p,int q){
    ll res=1;
    while(q){
        if(q&1)res=res*p%MOD;
        q>>=1;
        p=p*p%MOD;
    }
    return res;
}
bool tpow(ll p,int q){
    ll res=1;
    while(q){
        if(q&1){
            res=res*p;
            if(res>=MOD)return 1;
        }
        q>>=1;
        p=p*p;
        if(p>=MOD&&q>0)return 1;
    }
    return 0;
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        bool flag=0;
        int n,p;
        scanf("%d%d",&n,&p);
        vector<int>k;
        int temp;
        for(int i=0;i<n;i++){
            scanf("%d",&temp);
            k.push_back(temp);
        }
        sort(k.begin(),k.end());
        ll res=1;
        int prek=k.back();
        k.pop_back();
        int nowk;
        while(!k.empty()){
            nowk=k.back();
            k.pop_back();
            if(!flag) {
                if((tpow(p, prek - nowk)&&res!=0)||res*qpow(p,prek-nowk)>=MOD){
                    flag=1;
                    res=res*qpow(p,prek-nowk)%MOD;
                    res = (res - 1 + MOD) % MOD;
                }
                else {
                    res=res*qpow(p,prek-nowk);
                    if (res>0) {
                        res = res-1;
                    } else {
                        res = 1;
                    }
                }
            }
            else{
                res = res * qpow(p, prek - nowk) % MOD;
                res = (res - 1 + MOD) % MOD;
            }
            prek = nowk;
        }
        res=res*qpow(p,prek)%MOD;
        printf("%lld\n",res);
    }
    return 0;
}

总结

​ 理解能力还是不足,运用不够灵活,差点学死了....

posted @ 2020-06-05 22:53  dialectics  阅读(254)  评论(1编辑  收藏  举报