2020.11.27 考试题解

T1

题目描述:

“OI-Fitter 健身训练营”的总教练正在为训练的组织问题发愁。按照流程安
排,营员们 2 人一组进行训练,但悬殊的个体差异让分配队伍的问题变得棘手。
为了评估营员之间差异的大小,总教练设立了这样一个指标:设营员共有 N。
名,每个人的健壮程度设为一个正整数 x[i] (i=1,2,3......)。将这N个数两两求
差并取绝对值,可得到如下的 M=C(n,2) 个数:
| x[s] - x[t] | ,1≤s<t≤N。
最终的“差异指标”设为这M个数的中位数。若M为偶数,则中位数视为第 M /2 小的数。
现给出营员的数量N和每人的健壮程度,请你帮总教练求出他们的“差异指标”。

输入格式:

每个测试文件含3~5组测试数据,以文件结束符 (EOF) 表示输入结束。
对于每组数据:
第一行是一个正整数 N;
第二行是 N 个正整数,第i个数为 x[i]。

输出格式:

对于每组测试数据,输出一行表示答案。

样例输入:

4
1 3 2 4
3
1 10 2

样例输出:

1
8

数据规模:

(30%)的数据:(3leq;N ;leq1000)

(100%)的数据:(3leq;Nleq;100,000,1leq; x_i;leq10^9)

分析:

我们先考虑朴素算法,将每一个(;leftvert x_s-x_t ightvert;)求出来,再排一遍序,找出中位数,但这样的时间复杂度为(;O(n^2);)的,肯定会超时,我们想如何去优化。

似乎,这条路走到头了。我们肯定是无法在时间限制(;1s;)内求出来所有的(;leftvert x_s-x_t ightvert;)的,那我们是否可以不将其具体求出而能找到我们需要的答案的方法呢?

我们都想到了排序,而且(;leftvert x_s-x_t ightvert;)(;x_i;)在序列中的顺序是没有关系的,反正每两个之间都有。那么我们为什么不可以将原(;x;)数组从小到大排序,之后……二分答案?

好像是可以的。

我们可以二分出一个值(;val;),表示我们要求的答案中位数,在数组中寻找比每个(;x_i;)(;val;)的数的个数(;cnt;),如果比(;n imes (n-1)div 4;)大,那么就表示我们二分的(;val;)值小了,继续二分右区间,反之,二分左区间。很显然,(;cnt;)是单调递增的。

当然,即使小了,(;val;)也是我们的可能答案之一。

(Code)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
#define ll long long
#define il inline
#define vocaloid(v) (v>='0'&&v<='9')
template <typename T>
il void read(T &x)
{
	x=0;char v=getchar();
	while(!vocaloid(v)) v=getchar();
	while(vocaloid(v)) {x=(x<<1)+(x<<3)+v-'0';v=getchar();}
}
ll n,m,a[maxn],ans,l,r,mid;
il bool check(int val)
{
	ll cnt=0;
	for(int i=0;i<n;i++)
		cnt+=n-(lower_bound(a,a+n,a[i]+val)-a);
	return cnt>m;
}
int main()
{
	freopen("median.in","r",stdin);
	freopen("median.out","w",stdout);
	while(scanf("%lld",&n)==1)
	{
		m=n*(n-1)>>2;
		for(int i=0;i<n;i++) read(a[i]);
		sort(a,a+n);
		l=0,r=a[n-1]-a[0];
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(check(mid)) ans=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%lld
",ans);
	}
	return 0;
}

T3 POJ-3666

分析:

取得最小值的必要条件:对于任意的(;i;),都存在(;j;)使得(;x_i=a_j;)。其中(;1leq i,jleq N;)。也就是
说,(;x_i;)的取值范围只可能是(;a_1,a_2,ldots,a_N;) 构成的集合(下文记为 (;S))。

我们先考虑单调不下降的情况。

我们记(;b;)数组来储存出现过的所有数(去了重),共有(;m;)个,并从小到大排序,然后进行dp。

我们记(;f_{i,j};)为前(;i;)个数单调不下降,且第(;i;)个数凑成(;b_j;)时的最小花费。

转移方程为:

[f_{i,j}= min{f_{i-1,k}+ leftvert a_i-b_j ightvert} ]

[1leq; i;leq n 1leq;k;leq j;leq m ]

但我们发现这样的化,时间复杂度为(;O(n^3);)的。

由于(;j;)是递增的,(;k;)也是,我们进行了很多重复的操作,所以我们可以用一个(;Min;)来储存当前(;1;)到当前(;j;)(f_{i-1,k};)的最小值,可优化成:

[f_{i,j}= Min+ leftvert a_i-b_j ightvert ]

[1leq; i;leq n 1leq; j;leq m ]

这样,就优化成了(;O(n^2);)的,就可以通过啦!

(Code)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+7;
#define inf 0x3f3f3f3f3f3f3f3f
#define il inline
#define vocaloid(v) (v>='0'&&v<='9')
#define ll long long
template <typename T>
il void read(T &x)
{
	x=0;char v=getchar();
	while(!vocaloid(v)) v=getchar();
	while(vocaloid(v)) {x=(x<<1)+(x<<3)+v-'0';v=getchar();}
}
ll a[maxn],A[maxn],b[maxn],ans;
ll f[maxn][maxn],n,len=-inf,m;
il ll DP()
{
	for(int i=1;i<=n;i++)
	{
		ll Min=inf;
		for(int j=1;j<=m;j++)
		{
			Min=min(Min,f[i-1][j]);
			f[i][j]=Min+abs(a[i]-b[j]);
		}
	}
	ll res=inf;
	for(int i=1;i<=m;i++) res=min(res,f[n][i]);
	return res;
}
int main()
{
	freopen("function.in","r",stdin);
	freopen("function.out","w",stdout);
	read(n);
	for(int i=1;i<=n;i++) read(a[i]),A[i]=a[i];
	sort(A+1,A+1+n);
	for(int i=1;i<=n;i++)
		if(A[i]!=len) b[++m]=A[i],len=A[i];
	ans=DP();
	memset(f,0,sizeof(f));
	for(int i=1;i<=n/2;i++) swap(a[i],a[n-i+1]);
	ans=min(ans,DP());
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/MIKU5201314/p/14086751.html