2025-6-15模拟测验

自我评价:Tang 完了。

题解

题解中包含题面描述,但不包含大样例。

T1 怎么又是先增后减(why)

描述

青蛙又给了周欣一个长为 \(N\) 的正整数序列 \(A_i\),周欣可以进行若干次操作,每次可以选择一个位置 \(i\),满足 \(1 \leq i \leq N – 1\),将 \(A_i\) 的值和 \(A_{i+1}\) 的值进行交换。

设经过这若干次操作后的序列为 \(B_i\),那么需要存在一个整数 \(k \in [1,N]\),满足:

  • 区间 \([1,k]\) 构成的子序列 \([B_1,B_2, \cdots ,B_k]\) 是一个非严格单调递增的序列,即相邻两项允许相等,但是左边元素不能大于右边元素。

  • 区间 \([k,N]\) 构成的子序列 \([B_k,B_{k+1}, \cdots ,B_N]\) 是一个非严格单调递减的序列,即相邻两项允许相等,但是左边元素不能小于右边元素。

周欣想知道至少需要对序列进行多少次上述操作后,这个要求才能得以满足,他把这个问题交给了你来解决。

输入

第一行一个整数 \(N\),表示序列长度。

第二行 \(N\) 个整数表示 \(A_1,A_2,\cdots,A_N\)

输出

输出一个整数,表示答案,即最少的操作次数。

样例

\(3\) 组。

数据范围

对于 \(20\%\) 的数据,满足 \(N \leq 10\)

对于 \(50\%\) 的数据,满足 \(N \leq 5000\)

对于 \(100\%\) 的数据,满足 \(1 \leq N \leq 10^5, 1 \leq A_i \leq 10^5\)

题解

我们令 \(x\) 为当前序列中最小的数字。

以样例 3 1 4 1 5 9 2 为例子,\(x=1\)(假设为第一个 \(1\))。

我们知道,\(x\) 必须被移动到最左边或最右边。样例中,往左移动需要 \(1\) 步,但往右移动需要 \(4\) 步(不是 \(5\) 步,因为相同的 \(1\) 不需要交换)。

于是我们得到一个解题思路:

对于每个数,计算出左边严格比他大的有多少个,记为 \(l\);再计算出右边严格比他大的有多少个,记为 \(r\)。然后将 \(ans\) 累加 \(\min(l,r)\) 即可。

暴力统计的复杂度为 \(\mathcal{O}(n^2)\),可以得到 \(50\%\) 的分数。

使用树状数组优化,复杂度为 \(\mathcal{O}(n \log n)\),可以得到 \(100\%\) 的分数。

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x = 0, f = 1;
	char ch = getchar();
	while(!isdigit(ch)){
		if(ch == '-') f = -1;
		ch = getchar();
	}
	while(isdigit(ch)){
		x = (x<<1) + (x<<3) + (ch^48);
		ch = getchar();
	}
	return x * f;
}
const int N = 1e5 + 10;
int n, a[N], t[N], LEFT[N], RIGHT[N];
inline int lowbit(int x){
	return x & (-x);
}
inline void add(int x){
	while(x <= N){
		t[x]++;
		x += lowbit(x);
	}
	return;
}
inline int query(int x){
	int ans = 0;
	while(x){
		ans += t[x];
		x -= lowbit(x);
	}
	return ans;
}
signed main(){
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	for(int i = 1; i <= n; i++){
		add(a[i]);
		LEFT[i] = query(N) - query(a[i]);
	}
	memset(t, 0, sizeof(t));
	for(int i = n; i >= 1; i--){
		add(a[i]);
		RIGHT[i] = query(N) - query(a[i]);
	}
	long long ans = 0;
	for(int i = 1; i <= n; i++) ans += min(LEFT[i], RIGHT[i]);
	cout << ans << endl;
	return 0;
}

T2 美食节(festival)

描述

在 OI 国,所有城市排成了一个序列,从左往右分布是编号为 \(1,2,\cdots,n\) 的城市。

青蛙今天在 OI 国旅游,一开始他在编号为 \(x\) 的城市。

OI 国准备举办 \(n\) 次美食节,第 \(i\) 次的美食节在编号区间 \([l_i,r_i]\) 内的城市上举办。在每次美食节开始之前,青蛙可以在 OI 国中从当前他在的城市旅游到另一个城市,从编号为 \(a\) 的城市移动到编号为 \(b\) 的城市会让他花费 \(|a-b|\) 元钱。

如果一次美食节举办时,青蛙不在美食节举办的范围内,此时我们假设青蛙当前所在城市到美食节举办范围内城市的最短距离为 \(k\),则青蛙会花费 \(k\) 元,请人帮他从最近的美食节举办的城市买美食。

为了让青蛙省钱,你需要求出所有的美食节举办结束后,青蛙最少的花费。

输入

第一行两个正整数 \(n,x\)

接下来 \(n\) 行,每行两个正整数 \(l_i,r_i\)

输出

输出一个整数,表示答案。

样例

\(1\) 组。

数据范围

对于 \(20\%\) 的数据,满足 \(n,x,l_i,r_i \leq 10\)

对于 \(50\%\) 的数据,满足 \(n \leq 2000\)

对于 \(100\%\) 的数据,满足 \(1 \leq n \leq 5 \times 10^5, 0 \leq x,l_i,r_i \leq 10^9\)

题解

考虑令 \(dp_{i,j}\) 表示第 \(i\) 个活动后在 \(j\) 的最小疲劳值。

显然的对于每个 \(i\),先赋初始值为 \(i-1\) 部分,然后把不在范围内的 \(j\) 加上距离作为代价。如果要移动,则使用 \(dp_{i,j}+1\) 更新 \(dp_{i,j-1}\)\(dp_{i,j+1}\)

然后,我们对于每个 \(i\),把 \(dp_{i,j}\) 看成关于 \(j\) 的函数。可以证明这个函数最多由 \(3\) 个部分组成,斜率分别为 \(-1,0,1\),函数下凸。

于是我们只需要维护最下面的一段即可。令 \(l,r\) 代表这段区间的范围,对每个节日动态更新即可,复杂度 \(\mathcal{O}(n)\)

证明

考虑使用归纳法

\(i = 0\) 时,\(f(j) = |j-x|\),满足要求。

\(i\) 增大时,“把不在范围内的 \(j\) 加上距离作为代价”部分会使函数加上一个差分为 \(-1,0,1\) 的函数,差分变为 \(-2,-1,0,1,2\)

但第二部分计算移动时,会让函数的差分绝对值不超过 \(1\),差分又变成 \(-1,0,1\)

证毕。

代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
	int n, x; cin >> n >> x;
	int l = x, r = x; //维护下凸函数最下方的区间范围
	int ans = 0;
	for(int i = 1; i <= n; i++){
		int li, ri; cin >> li >> ri;
		if(li > r){
			ans += li - r;
			l = r, r = li;
		}else{
			if(ri < l){
				ans += l - ri;
				r = l, l = ri;
			}else{
				l = max(l, li);
				r = min(r, ri);
			}
		}
	}
	cout << ans << endl;
	return 0;
}

T3 环上合并(ring)

咕咕咕。

来源链接:https://www.cnblogs.com/zhang-kevin/p/18929612

© 版权声明
THE END
支持一下吧
点赞13 分享
评论 抢沙发
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复

    暂无评论内容