Codeforces (Div. 3) G. Trader Problem

题目链接: G. Trader Problem

这道题,首先我们可以观察到,能合并的区间一定是连续的,并且k越大,能够合并的区间越多,因此我们需要将提问离线并排序,按照k的大小将区间逐个合并

这道题巧妙的应用了并查集,因为最后的答案一定是选择n个数字,而我们选择的数字个数,是每个区间中从右往左sz[x]的大小,从右往左是因为要选最大的数。因此还要求个前缀和

#include 
#define ll long long
using namespace std;
typedef pair PII;
const int N = 4e5+10;
PII a[N], q[N]; 
ll ans[N], sum, pre[N]; //前缀和 
map> mp;
int fa[N], sz[N];
int find(const int &x){
	if(x!=fa[x]) return fa[x] = find(fa[x]);
	return x;
}
int main() {
	int n, m, query; scanf("%d%d%d", &n, &m, &query);
	for(int i = 1; i <= n+m; i++) scanf("%lld", &a[i].first), a[i].second = i;
	for(int i = 1; i <= query; i++) scanf("%lld", &q[i].first), q[i].second = i;
	sort(a+1, a+n+m+1); //将数组排序
	sort(q+1, q+query+1); //离线, 区间按照q的大小依次合并 
	
	for(int i = 1; i <= n+m+1; i++) {
		pre[i] = pre[i-1] + a[i].first;
		if(a[i].second <=n) sum += a[i].first, sz[i] = 1;  //注意只有能够交易的sz置1 
		fa[i] = i;
		ll tmp = a[i].first - a[i-1].first;
		mp[tmp].push_back(i);
	}
	
	auto it = mp.begin();
	for(int i = 1; i <= query; i++){
		while(it != mp.end() && it->first <= q[i].first){ //当这个区间的差值<=k, 就说明这个区间内的点都可以合并 
			for(auto v:it->second) {
				int x = v, y = v-1; //合并x, y
				x = find(x), y = find(y); //root为合并的区间的末端点, sz代表这个区间中有多少个可以被交易的(即原数组)商品 
				//pre[x] - pre[x-sz[x]], x为区间右端点,  由于排好序了, 就选择最右边的sz[x]个数即可 
				sum -= pre[x] - pre[x-sz[x]] + pre[y] - pre[y-sz[y]]; //答案原来的区间的贡献 
				fa[y] = fa[x], sz[x] += sz[y]; //一定是左端点合并到右端点
				sum += pre[x] - pre[x-sz[x]]; 
			}
			it++;
		}
		ans[q[i].second] = sum;
	}
	for(int i = 1; i<=query; i++){
		printf("%lld\n", ans[i]);
	}
	
	return 0;
}

 然后还要注意并查集合并,是左边的区间 合并 到右边的区间

还有空间要开4e5!!!起初空间开了2e5就RE了

你可能感兴趣的:(算法)