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

探讨了如何将一个由特定形式元素组成的序列划分为两部分,目标是最小化这两部分之间的差值。通过排序和进位模拟,算法有效地找到最佳划分点,适用于竞赛编程中的优化问题。

题意:

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

题解:

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

所以每次去寻找相等的左右部分即可,若找不到,则直接求差值即可。
因为如果模拟进位操作的话比较困难,而pki=p∗pki−1=p2∗pki−2p^{k_i}=p*p^{k_i-1}=p^2*p^{k_i-2}pki=ppki1=p2pki2,所以将pkip^{k_i}pki降次,从而求是否有pxp^xpxpki−xp^{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、付费专栏及课程。

余额充值