P3054 [usaco12open] running laps s 题解

 

题目链接

此题总体思路为逆序对,可以使用归并排序,树状数组和线段树等算法。

本文使用归并排序的思路来解决逆序对。

暴力算法时间复杂度为 $O(n^2)$。

期望得分 $48$ 分。

#include <bits/stdc++.h>
using namespace std;
#define N 100001
int a[N];
int n,l,c;
int main(){
	scanf("%d %d %d",&n,&l,&c);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	sort(a+1,a+n+1);
	long long ans=0;
	for(int i=n;i>1;i--){
		for(int j=i-1;j>=1;j--){
			ans+=((a[i]-a[j])*l)/a[n]; 
		}
	}
	printf("%lld",ans);
	
}

首先我们考虑每头牛跑的圈数,则先需要求出所有牛可以跑步的最大时间 $t$。

某头牛跑完时,所有的牛都停下来。

因此只要速度最快的牛完成了赛段,则所有的牛都需要停下,用快速排序求出速度最大值 $V_{max}$。

不难求出 $t=\frac{l \times c}{V_{max}}$。

设第 $i$ 头奶牛跑了 $a_i$ 圈, $a_i =v_i\times (l\div V_{max})$ (数学公式可由上式推出) 。

我们对圈数进行排序,则绕圈数量为

$n=a_i -a_j=(v_i-v_j)\times l \div V_{max}$ $(1\leq i<j\leq n)$。

但注意一个魔鬼细节

如果圈数为小数时则会多算一圈,举个例子如果 $a_i=3.6,a_j=2.9$ 时,这个式子就会多算一个 $1$,我们可以对 $a_i$ 的余数部分求出逆序对,可以将所有多算出来的部分求出。

Q

为什么要求出余数部分,而不求小数部分?

A

因为小数部分精度问题复杂,余数方便处理。

代码如下。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,l,c;
int v[114514];
int cpy[114514];
int guich[114514];
inline int read(){//快读
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-'){
			f=-1;
			c=getchar();
		}
	}
	while(c>='0'&&c<='9'){
		x=(x<<3)+(x<<1)+(c^48),c=getchar();
	}
	return x*f;
}
int tmp[114514];
int ans=0,t=0,gary=0;
inline void merge(int l,int mid,int r){//归并板子
	int i=l,j=mid+1,t=l;
	while(i<=mid&&j<=r){
		if(cpy[i]>cpy[j]){
			ans+=mid-i+1;
			tmp[t++]=cpy[j++];
		}
		else tmp[t++]=cpy[i++];
	}
	while(i<=mid) tmp[t++]=cpy[i++];
	while(j<=r) tmp[t++]=cpy[j++];
	for(int i=l;i<=r;i++) cpy[i]=tmp[i];
}
inline void mergesort(int l,int r){
	if(l>=r) return ;
	int mid=l+(r-l)/2;
	mergesort(l,mid);
	mergesort(mid+1,r);
	merge(l,mid,r);
//	return ans;
}
int v_max;
signed main(){//使用了#define int long long 所以将int修改为signed
	n=read();
	l=read();
	c=read();
	for(register int i=1;i<=n;i++){
		v[i]=read();
		v_max=max(v_max,v[i]);
	}
	sort(v+1,v+1+n);
	for(register int i=1;i<=n;i++){
		cpy[i]=(l*v[i])%v_max;//余数部分
		guich[i]=(l*v[i])/v_max;//整数部分
	}
	for(register int i=1; i<=n; i++) {
		gary+=(i-1)*guich[i]-t;//计算未处理前的值
		t=t+guich[i];//计算所有多加的值
	} 
	mergesort(1,n);
	gary-=ans;
	printf("%lld\n",gary);
	return 0;
}