数据结构与算法课程设计——CSP202112

CSP202112

01 序列查询

题目

在这里插入图片描述

标签

​ 模拟?二分?差分?区间计算?

输入输出

在这里插入图片描述

数据范围

在这里插入图片描述

思路

​ 考虑到边界情况,开辟一个大小至少为n+2的数组A,且A[0]=0, A[n+1]=N。

​ 如何求 f ( x ) f(x) f(x)?

  • 使用暴力,时间复杂度显然是 O ( N n ) O(Nn) O(Nn)
  • 注意到A中的数严格递增, f ( x ) = i    ⟺    A i ≤ x < A i + 1 f(x)=i\iff A_i\le x<A_{i+1} f(x)=iAix<Ai+1,可以考虑使用二分求出 f ( x ) f(x) f(x),时间复杂度为 O ( N l o g n ) O(Nlogn) O(Nlogn)
    • 理论上还可以进一步优化
  • 又注意到A中的数都是整数,有 f ( x ) − f ( x − 1 ) = I [ ∃   i ( A [ i ] = x ) ] f(x)-f(x-1) = I[\exist ~i(A[i]=x)] f(x)f(x1)=I[ i(A[i]=x)],即区间 [ A [ i ] , A [ i ] + 1 , . . . , A [ i + 1 ] ) [A[i],A[i]+1,...,A[i+1]) [A[i]A[i]+1...A[i+1])满足题目所给提示。
    • 使用瞪眼法也可以得到相同的结论,而且更加简单。

注意

代码

  • 使用瞪眼法的代码
#include <bits/stdc++.h>
using namespace std;

int main( ){
	int n, N;
	cin >> n >> N;
	vector<int> a(n+2);
	a[0] = 0; a[n+1] = N;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	
	int ans = 0;
	for (int i = 0; i <= n; ++i) {
		ans += (a[i+1] - a[i]) * i;
	}
	cout << ans << endl;
	return 0;
}
  • 使用二分的代码
#include <bits/stdc++.h>
using namespace std;

int main( ){
	int n, N;
	cin >> n >> N;
	vector<int> a(n+2);
	int cur = 0;
	a[0] = 0; a[n+1] = N;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	int ans = 0;
	for (int i = 0; i < N; ++i) {
		ans += upper_bound(a.begin(), a.end(), i) - a.begin() - 1;
	}
	cout << ans;
	return 0;
}

02 序列查询新解

题目

在这里插入图片描述

标签

​ 模拟?区间计算。

输入输出

在这里插入图片描述

数据范围

在这里插入图片描述

思路

​ 题目明确表示了:禁止二分!

  • 当然如果使用二分或者暴力还是能有70分

​ 划分 f ( x ) f(x) f(x)的方法与上一题相同,但是由于N可能很大,无法使用二分。

​ 注意到,对于给定的n和N, g ( x ) g(x) g(x)的区间划分是确定的,而 f ( x ) f(x) f(x)的区间划分还需要由 A A A数组确定。由于 f ( x ) 和 g ( x ) f(x)和g(x) f(x)g(x)的定义域相同,这题实际考察点是:如何将 0 , 1 , … , N − 1 {0, 1, \dots, N-1} 0,1,,N1划分为多个区间,使划分得到的每个区间S满足 ( ∀ x , y ∈ S ) ( f ( x ) = f ( y ) ) & ( g ( x ) = g ( y ) ) (\forall x,y\in S)(f(x)=f(y))\&(g(x)=g(y)) (x,yS)(f(x)=f(y))&(g(x)=g(y))。同时,计算每个区间的长度以及 ∣ f ( x ) − g ( x ) ∣ |f(x)-g(x)| f(x)g(x)的值。

  • 区间数量是否过多?
    • 实际上,区间数量的一个上界为 3 n + 3 3n+3 3n+3。这是显然的,因为可以确定 g ( x ) g(x) g(x)的区间划分个数 N / ⌊ N n + 1 ⌋ ≤ N / ( N n + 1 + 1 ) ≤ n + 1 N/\lfloor\frac{N}{n+1}\rfloor\le N/(\frac{N}{n+1}+1)\le n+1 N/n+1NN/(n+1N+1)n+1。同时 f ( x ) f(x) f(x)划分出的每个区间,仅在一个端点落在 g ( x ) g(x) g(x)划分的区间内部时,使区间总数增加1,即最多产生 2 ( n + 1 ) 2(n+1) 2(n+1)个区间。所以,区间总数不会多于 3 n + 3 3n+3 3n+3.
    • 考虑到每个区间的右端点一定是另一个区间的左端点(除最后一个),其实一个紧上界为 2 n + 3 2n+3 2n+3
  • 如何确定每个区间的长度?
    • 鉴于我们已经计算出了区间数量的上界,且这个值并不算大,我们可以将所有区间的左端点(或右端点)记录下来,同时记录其是由 f 或 g f或g fg分割得到的,以及其对应值 f ( x ) 或 g ( x ) f(x)或g(x) f(x)g(x)。然后将按照端点位置排序,就得到了所有的新区间。
      • 排序时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),求和为 O ( n ) O(n) O(n),总复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
        • 实际运行其实与另一种方法效率近似。
        • 空间复杂度稍逊于下一种方法。
    • 也可以从 f ( x ) ( 或 g ( x ) ) f(x)(或g(x)) f(x)(g(x))分割得到的区间开始,依次判断 g ( x ) ( 或 ( f ( x ) ) g(x)(或(f(x)) g(x)((f(x))分割得到的每一个区间与其相交情况
      • 线性时间复杂度,即 O ( n ) O(n) O(n)
      • 代码逻辑很复杂。

注意

  • r = ⌊ N n + 1 ⌋ r=\lfloor\frac{N}{n+1}\rfloor r=n+1N可能使 g ( x ) g(x) g(x)分割得到的最后一个区间的右端点>N,要将其设为N。
  • 耐心!

代码

  • 从g(x)分割的区间开始,判断f(x)分割得到的每个区间与其相交情况
#include <bits/stdc++.h>
using namespace std;

int r;
vector<int> A;

int main( ) {
	int n, N;
	cin.tie(0);
	ios::sync_with_stdio(false);
	cin >> n >> N;
	A.resize(n+2);
	for (int i = 1; i <= n; ++i) {
		cin >> A[i];
	}
	A[n+1] = N;
	r = N / (n+1);
	int cur = 0;
	long long ans = 0;
/*******************
	每段长度为r,共有k段,满足k*r>=N
		--> 外循环条件
	第i段为[i*r, (i+1)*r), 值为i, 且右端点不超过N
		--> right = min(N, (i+1) * r) 
	考虑第i段与[A[cur], A[cur+1])的包含关系:
		当A[cur] < right时, 有交, 否则没有
			--> 内循环条件
		相交部分贡献 
			-->(min(A[cur+1], right) - max(A[cur], i*r)) * abs(i-cur)
		cur--必需,否则会丢失一段相交 
*******************/ 
	for (int i = 0; i * r < N; ++i) {
		int right = min(N, (i+1) * r);
		while (A[cur] < right) {
			ans += (min(A[cur+1], right) - max(A[cur], i*r)) * abs(i-cur);
			cur++;
		}
		cur--;
	}
	cout << ans;
	return 0;
} 
  • 排序左端点后计算
#include <bits/stdc++.h>
using namespace std;

typedef struct Segment{
	int ind; //左端点所在位置
	int val; //对应f(ind)或g(ind)
	bool f_div;	//true时,为f分割得到	
	bool operator<(const Segment& s) {
		return ind < s.ind;
	}
}S;

int r;
vector<S> A;

int main( ) {
	int n, N;
	cin.tie(0);
	ios::sync_with_stdio(false);
	cin >> n >> N;
	A.resize(n+2);
	for (int i = 1; i <= n; ++i) {
		cin >> A[i].ind;
		A[i].f_div = true;
		A[i].val = i;
	}
	A[n+1].ind = N; A[0].ind = 0;//不需要A[n+1]的val
	A[n+1].f_div = true; A[0].f_div = true;
	r = N / (n+1);
	for (int i = 0, t = 0; t < N; ++i, t += r) {
		S tp;
		tp.ind = t;
		tp.val = i;
		tp.f_div = false;
		A.push_back(tp);
	}
	sort(A.begin(), A.end());
	long long cur_f = 0, cur_g = 0, ans = 0;
	for (int i = 1; i < A.size(); ++i){
		ans += abs(cur_f - cur_g) * (A[i].ind - A[i-1].ind);
		if (A[i].f_div) cur_f = A[i].val;
		else cur_g = A[i].val;
	}
	cout << ans;
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值