牛客网[CF1043] Train Hard, Win Easy(前缀和好题)

本文解析了一道关于n人做题,m对人不能组队的优化问题。通过理解题意,提出利用排序和前缀和技巧降低时间复杂度,最终给出nlogn的解决方案。关键在于理解题目中如何根据时间差分配队伍组合,以求得最小总答题时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Train Hard, Win Easy

原题链接


首先这道题最大的难点就是理解题意,各种翻译软件翻译出来的几乎完全看不懂,只能慢慢的借助样例解释慢慢的来了解题意。

题意大概如下:

总共有两道题,给你n个人做每道题目的时间,之后是m个关系,这m对人无法组队,两道题目两个人组队一人一道,每次组队的时候要使总时间最少,求每个人和其他能组队的人组队的得分和
数据范围:2<=n<=3e5,0<=m<=3e5

首先考虑暴力是否可行
要求没两个人的两两组合,n^2,显然不行

再开始想正解

依据题意
我们先假设m为0
那么

a n s i = ∑ i n m i n ( a i + b j , b i + a j ) ansi= \sum_{i}^{n} min(ai+bj,bi+aj) ansi=inmin(ai+bj,bi+aj)

也就是说
当ai+bj<bi+aj时对答案的贡献是ai+bj
反之则是bi+aj

我们将其移下项
bj-aj<bi-ai时对答案的贡献是ai+bj
现在已经看到正解的尾巴了

如果我们将每个人的bi-ai进行从小到大排序,在这个人前面的人(记为j)的bj-aj都会小于bi-ai

那么对答案的贡献就是ai+bj,也就是前面所有人的bj都和ai进行配对,然后我们只要借助前缀和进行优化
就可以求出前i-1个人(排好序的数组里的编号)和i组队的最小答题时间和,即为sumb+ai*(i-1)
后面的同理,应该为suma+ai*(n-i)
这里的suma是从n到1的a[i]的前缀和
sumb是1到n的b[i]的前缀和

然后最后减去不能组队的情况,就解决了

代码如下(时间复杂度nlogn):

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long 
using namespace std;
const int maxn=3000005;
int n,m;
ll a[maxn],b[maxn];
struct num_node{
	ll num;
	int id;
}s[maxn];
ll ans[maxn];
bool cmp(num_node a,num_node b){
	return a.num<b.num;
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;++i){
		ll x,y;
		scanf("%lld %lld",&x,&y);
		a[i]=x;b[i]=y;
		s[i].num=y-x;
		s[i].id=i;
	}
	sort(s+1,s+n+1,cmp);
	ll suma=0,sumb=0;//这里必须开long long 
	for(int i=1;i<=n;++i){
		ans[s[i].id]+=a[s[i].id]*(i-1)+sumb;
		sumb+=b[s[i].id];
	}
	for(int i=n;i>=1;--i){
		ans[s[i].id]+=b[s[i].id]*(n-i)+suma;
		suma+=a[s[i].id];
	}
	while(m--){
		ll x,y;
		scanf("%lld %lld",&x,&y);
		ans[x]-=min(b[x]+a[y],b[y]+a[x]);
		ans[y]-=min(b[x]+a[y],b[y]+a[x]);
	}
	for(int i=1;i<=n;++i){
		printf("%lld",ans[i]);
		if(i!=n)printf(" ");
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值