day 5 模拟赛 3

模拟赛 3

T1 游戏

有 n 种游戏,第 i 个价格为 \(c_i\) 元,你第 0 天有 a 元钱,每天(不含第 0 天)可以获得 b 元钱,每天至多购买 1 种游戏,求买完所有游戏所需的最小天数。

显然,反正每天获得的钱都固定,如果你能买尽量就买,因为一天不能买多个,所以我们把游戏的价格排序,从小到大地贪心购买,复杂度 \(O(n)\),数据较弱。

T2 Median

定义一个含 m 个数列的中位数为把数列从小到大排序后的第 \(m/2+1\) 个数(向下取整)。

求一个长度为 n 的序列中 \(n(n+1)/2\) 个子区间中中位数所构成数列的中位数。

部分分:直接最暴力的枚举、排序,复杂度 \(O(n^3\log n)\)。-------------------------------30分

用队顶堆优化,每次移动 r 时更新答案,复杂度 \(O(n^2)\)。-------------------------------60分

正解:本题是经典套路,考虑二分答案,若答案是 mid,那么它是最终答案需要满足有 \(n(n+1)/2/2+1\) 个区间求出的中位数比 mid 大,这样 mid 在最终序列中才处在中间位置。

考虑如何求有多少个中位数比 mid 大,这里也用一个经典的转化,设比 mid 大的数为 1,比 mid 小的数为 -1,那么一个区间的中位数大于等于 mid 就等价于这个 0-1 区间的和大于等于 0。

我们考虑求出这个 0-1 数列的前缀和 s,发现区间和大于等于 0,又等价于 \(s_{l-1}\ge s_r\),所以我们可以建一个权值树状数组,把值存到下标中,存的内容是该下标的数量,枚举一次原数组,每次查询 \(1~a_i\) 中树状数组的和,也就是满足上面式子的个数,之后把新枚举的值加入树状数组,方便后续查询。

时间复杂度 \(O(n\log^2n)\)。-------------------------------100分

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,cnt,a[100005],b[100005],c[100005],s[100005];
int tr[200005];
int lowbit(int x){
    return x&(-x);
}
void update(int x,int k){
    for(;x<=n*2+5;x+=lowbit(x))tr[x]+=k;
}
int query(int x){
    int ans=0;
    for(;x;x-=lowbit(x))ans+=tr[x];
    return ans;
}
signed main(){
	freopen("median.in","r",stdin);
	freopen("median.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	if(n<=100){
		for(int i=1;i<=n;i++){
			for(int j=i;j<=n;j++){
				for(int k=i;k<=j;k++)b[k-i]=a[k];
				sort(b,b+j-i+1);
				c[++cnt]=b[(j-i+1)/2];
			}
		}
		sort(c+1,c+cnt+1);
		cout<<c[cnt/2+1];
		return 0;
	}
	int k=n+5;
	int l=0,r=1e9;
	while(l<r){
		int mid=(l+r+1)/2;
		memset(tr,0,sizeof tr);
		s[0]=0;
		for(int i=1;i<=n;i++){
			if(a[i]>=mid)b[i]=1;
			else b[i]=-1;
			s[i]=s[i-1]+b[i];
		}
		int ans=0;
		update(s[0]+k,1);
		for(int i=1;i<=n;i++){
			ans+=query(s[i]+k);
			update(s[i]+k,1);
		}
		if(ans>=n*(n+1)/2/2+1)l=mid;
		else r=mid-1;
	}
	cout<<l;
	return 0;
}

T3 秋游

有 n 个未知的正整数 aᵢ(≤ bᵢ),分别去掉每个数后,剩下 n-1 个数的 gcd 会得到 n 个值。问在所有可能的 aᵢ 中,这 n 个 gcd 最多能有几种不同的数。

n 个数的 gcd 实际上就是分别分解质因数,求每个质数指数最小数的乘积。

最优的构造方式如下:

  • 所有出现过的质因子是最小的 k 个质数(2, 3, 5, 7, 11, …)这是显然的,不然容易超出 \(b_i\) 的限制。
  • 有一个数包含所有这些质因子(乘积最大)
  • 另外 k 个数,第 i 个数缺少第 i 个质因子(也就是乘积去掉一个质数)这样才能保证第一条。

我们发现,当 \(k>15\) 时,前 k 个质数的乘积 \(2*3*5*...\) 已经超过 long long,所有只需要枚举 k 在 1 到 \(\min(n-1,15)\) 之间,判断是否能满足 \(b_i\) 的限制即可。

构造出来的数列也就是 \(S,S/2,S/3,S/5,...\),共 \(k+1\) 项,其中 S 为前 k 个质数的积。

那么如何判断?我们贪心,把构造出来的 a 和给出的 b 排序,依次匹配,就是最有可能的方案。

复杂度 \(O(n\log n+nk)\)。-------------------------------100分

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,b[200005],a[20];
int prim[20]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47};
signed main(){
	freopen("trip.in","r",stdin);
	freopen("trip.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>b[i];
	sort(b+1,b+n+1);
	int ans=1;
	for(int k=1;k<=min(n-1,(int)15);k++){
		int p=1;
		for(int i=1;i<=k;i++) p*=prim[i];
		int cnt=0;
		a[++cnt]=p;
		for(int i=1;i<=k;i++) a[++cnt]=p/prim[i];
		sort(a+1,a+cnt+1);
		bool flag=1;
		for(int i=1;i<=cnt;i++){
			if(b[i]<a[i]){
				flag=0;
				break; 
			}
		}
		if(flag)ans=k+1;
	}
	cout<<ans;
	return 0;
}
posted on 2026-07-16 20:52  郑浩崎  阅读(4)  评论(0)    收藏  举报