Codeforces Round #760 (Div. 3) G. Trader Problem

9 篇文章 0 订阅
3 篇文章 0 订阅

G. Trader Problem

题意:
你手里有 n n n个物品,第 i i i个价值为 a i a_i ai。另外 m m m个物品,第 i i i个价值为 b i b_i bi。你可以把你手里的价值为 x x x的物品与不属于你的价值不超过 x + k x + k x+k的物品进行交换,交换后原本属于你的物品不再属于你并且可以在新的交换中被换回来,而作为交换,原本不属于你的那个物品现在属于你并且可以被用于新的交换中。现在有 q q q次询问,给定 k k k,求经过任意次数交换后属于你的物品的价值的最大值。

思路一: 自己想的复杂版
首先,考虑暴力的做法:对于一个给定的 k k k,我们从小到大遍历 n n n个物品,每次置换尽可能大的物品,但这样置换的复杂度 是 O ( n ∗ m ∗ q ) O(n*m*q) O(nmq)的;
考虑优化交换次数,首先肯定 将所有的 k k k离线,从小到大枚举 k k k去操作。
考虑以下的建图方式:将 n n n个物品从大到小排序,让相邻的物品之间建边,边权 是 两个物品价值只差;同理将 m m m个物品 之间建好边;对于 n n n个物品,每个物品和 m m m个物品中 最小的 比当前物品价值大的 物品连边,边权是 两者价值之差。
易得,如果是一个联通块的话,那么就可以用 n n n个物品中的最小的几个值 置换出 m m m个物品中最大的几个值,那么我们只要从小到大加边,并维护前几大的和即可。
这边两个集合的合并,我现在想到的是,可以用线段树合并来维护,但是代码较复杂,读者自行尝试。如果有更好的维护方法,读者可以在下面评论。

思路二: 简洁版
将所有物品(包括属于你的和不属于你的)按价值从大到小排序,第 i i i个价值为 v i v_i vi,然后依次遍历每一个物品。对于第 i i i件物品,如果存在 j ∈ [ i , n + m ] j ∈ [ i , n + m ] j[i,n+m],满足对任意 p ∈ [ i , j − 1 ] p ∈ [ i , j − 1 ] p[i,j1],有 v p − v p + 1 ≤ k v_p − v_{p+1} ≤ k vpvp+1k,且第 j j j个物品当前属于你,那么经过若干次交换,我们可以间接或直接地用价值为 v j v_j vj的物品交换得到价值为 v i v_i vi的物品。
在这个过程中,我们用并查集维护一段连续的区间,这区间的权值用前缀和来求得。

有疑问 见代码:

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<iostream>
#include<queue>
#include<stack>
#include<map>
#include<unordered_map> 
#include<set>
#include<algorithm>
using namespace std;
#define ll long long


const ll mod=1e9+7;
const ll maxn=1e7+5;
const ll INF=0x3f3f3f3f;
ll qp(ll x,ll y){
	ll ans=1;
	while(y){
		if(y%2)ans=ans*x%mod;x=x*x%mod;y/=2;}return ans;}
ll fmul(ll x,ll y){ 	//快速乘
    ll tmp=(x*y-(ll)((long double)x/mod*y+1.0e-8)*mod);
	return tmp<0?tmp+mod:tmp; 
}    
inline ll qread(){
    ll s=0,w=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
    return (w==-1?-s:s);}

int t;
int n,m,q;
struct node{
	int w,id;
}z[400050];
int sum[400050];
bool cmp(node a,node b){
	return a.w<b.w;
}
struct no{
	int id,k;
}p[200050];
bool cm(no a,no b){
	return a.k<b.k;
}
ll ans[200050];
ll sum1[400050];
struct nod{
	int l,r;
	ll w;
}zz[400050];

int fa[400050];
int find(int x){
	if(x==fa[x])return x;
	return fa[x]=find(fa[x]);
}
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>>qq;
int main(){
	scanf("%d%d%d",&n,&m,&q);
	ll now=0;
	for(int i=1;i<=n;i++){
		scanf("%d",&z[i].w);
		z[i].id=1;
	}
	for(int i=1;i<=m;i++){
		scanf("%d",&z[i+n].w);
		z[i+n].id=2;
	}
	sort(z+1,z+1+n+m,cmp);
	for(int i=1;i<=q;i++){
		scanf("%d",&p[i].k);
		p[i].id=i;
	}
	for(int i=1;i<=n+m;i++){
		fa[i]=i;
		zz[i].l=zz[i].r=i;
		zz[i].w=0;
		if(z[i].id==1)zz[i].w=z[i].w;
		now+=zz[i].w;
		sum1[i]=sum1[i-1]+z[i].w;
		sum[i]=sum[i-1];
		if(z[i].id==1)sum[i]++;
	}
	sort(p+1,p+1+q,cm);
	for(int i=2;i<=n+m;i++){
		qq.push(make_pair(z[i].w-z[i-1].w,i));
	}
	for(int i=1;i<=q;i++){
		while(!qq.empty()&&qq.top().first<=p[i].k){
			int id=qq.top().second;
			qq.pop();
			int x=find(id-1);
			int y=find(id);
			if(x==y)continue;
			now-=zz[x].w;now-=zz[y].w;
			fa[x]=y;
			zz[y].l=min(zz[y].l,zz[x].l);
			int num=sum[zz[y].r]-sum[zz[y].l-1];
			zz[y].w=sum1[zz[y].r]-sum1[zz[y].r-num];
			now+=sum1[zz[y].r]-sum1[zz[y].r-num];
		}
		ans[p[i].id]=now;
	}
	for(int i=1;i<=q;i++){
		printf("%lld\n",ans[i]);
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值