cf1362E. Johnny and Grandmaster,贪心+数学,一个序列划分成两部分,差最小

题意:

给一个序列,序列里的数都是 p k i p^{k_i} pki,把序列里的数划分为两个集合,使得差值最小。

题解:

首先对 k i k_i ki排序,从大到小排列,对于 p k i p^{k_i} pki来说,如果
∑ j = i + 1 n p k j > = p k i \sum_{j=i+1}^{n}p^{k_j}>=p^{k_i} j=i+1npkj>=pki,那么一定存在 l l l使得 ∑ j = i + 1 l p k j = = p k i \sum_{j=i+1}^{l}p^{k_j}==p^{k_i} j=i+1lpkj==pki。否则直接求差即可。
想象为 p p p进制,那么 p k i p^{k_i} pki ( 1000 ) p (1000)_p (1000)p,其中第 k i k_i ki位为 1 1 1,因为 k j ( j > i & & k j < k i ) k_j(j>i\&\&k_j<k_i) kj(j>i&&kj<ki),使 l l l为最大的下标使得加上 p k l p^{k_l} pkl后的和大于等于 p k i p^{k_i} pki,那么此时加上 p k j p^{k_j} pkj p p p进制状态一定是—— ( 00... ( p − 1 ) ( p − 1 ) ( p − 1 ) . . . ( p − 1 ) 00..00 ) p (00...(p-1)(p-1)(p-1)...(p-1)00..00)_p (00...(p1)(p1)(p1)...(p1)00..00)p,在最低位的 ( p − 1 ) (p-1) (p1)加上 1 1 1使得相等。

所以每次去寻找相等的左右部分即可,若找不到,则直接求差值即可。
因为如果模拟进位操作的话比较困难,而 p k i = p ∗ p k i − 1 = p 2 ∗ p k i − 2 p^{k_i}=p*p^{k_i-1}=p^2*p^{k_i-2} pki=ppki1=p2pki2,所以将 p k i p^{k_i} pki降次,从而求是否有 p x p^x px p k i − x p^{k_i-x} pkix

#include <bits/stdc++.h>
using namespace std;

const int mod = 1e9 + 7;

typedef long long ll;

ll qpow(ll x, ll n) {
	ll res = 1;
	while (n) {
		if (n&1) {
			res = (res*x) % mod;
		}
		x = (x*x) % mod;
		n /= 2;
	}
	return res;
}

void solve() {
	int n;
	ll p;
	cin >> n >> p;
	vector<ll> k(n);
	for (int i = 0; i < n; i++) {
		cin >> k[i];
	}
	if (p == 1) {
		cout << n%2 << endl;
		return ;
	}
	sort(k.begin(), k.end());
	vector<int> l, r;
	int idx = n-1;
	while (idx >= 0) {
		r.push_back(k[idx]);
		ll A = 1;
		int x = k[idx];
		int i;
		for (i = idx-1; i >= 0; i--) {
			l.push_back(k[i]);
			// 当前左边部分值为A*p^x,右边部分值为p^k[i]
			// A*p^x = A*(p^diff) * p^k[i]
			int diff = x - k[i];
			x = k[i];
			
			// 当前有A个p^x,转换为(A*p^diff个p^k[i])
			// 若A>=n,则剩下所有的数的和都小于当前左边部分值
			// log(n)<20,循环最多不超过20次
			while (diff && A < n) {
				A *= p;
				--diff;
			}
			
			--A;	//消耗当前的一个p^k[i]
			if (diff == 0 && A == 0) {
				break;
			}
		}
		idx = i-1;
	}
	
	ll ans = 0;
	for (auto x : r) {
		ans = (ans + qpow(p, x)) % mod;
	}
	
	for (auto x : l) {
		ans = (ans - qpow(p, x) + mod) % mod;
	}
	
	cout << ans << endl;
}

int main() {
	int t;
	cin >> t;
	while (t--) {
		solve();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值