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;
}
总结
理解能力还是不足,运用不够灵活,差点学死了....
标签:取模,Johnny,int,res,ll,个数,Grandmaster,MOD 来源: https://www.cnblogs.com/dialectics/p/13052739.html