【树状数组】P6278 [USACO20OPEN] Haircut G|普及+

本文涉及知识点

本博文代码打包下载
【C++】树状数组的使用、原理、封装类、样例

[USACO20OPEN] Haircut G

题目描述

Farmer John 由于对整理他难以整平的头发感到疲惫,于是决定去理发。他有一排 N N N 缕头发,第 i i i 缕头发初始时长度为 A i A_i Ai 微米( 0 ≤ A i ≤ N 0\le A_i\le N 0AiN)。理想情况下,他想要他的头发在长度上单调递增,所以他定义他的头发的“不良度”为逆序对的数量:满足 i < j i < j i<j A i > A j A_i > A_j Ai>Aj 的二元对 ( i , j ) (i,j) (i,j)
对于每一个 j = 0 , 1 , … , N − 1 j=0,1,\ldots,N-1 j=0,1,,N1,Farmer John 想要知道他所有长度大于 j j j 的头发的长度均减少到 j j j 时他的头发的不良度。


(有趣的事实:人类平均确实有大约 1 0 5 10^5 105 根头发!)

输入格式

输入的第一行包含 N N N
第二行包含 A 1 , A 2 , … , A N A_1,A_2,\ldots,A_N A1,A2,,AN

输出格式

对于每一个 j = 0 , 1 , … , N − 1 j=0,1,\ldots,N-1 j=0,1,,N1,用一行输出 Farmer John 头发的不良度。


注意这个问题涉及到的整数大小可能需要使用 64 64 64 位整数型存储(例如,C/C++ 中的“long long”)。

样例 #1

样例输入 #1

5
5 2 3 3 0

样例输出 #1

0
4
4
5
7

提示

样例解释:

输出的第四行描述了 Farmer John 的头发长度减少到 3 3 3 时的逆序对数量。
A = [ 3 , 2 , 3 , 3 , 0 ] A=[3,2,3,3,0] A=[3,2,3,3,0] 有五个逆序对: A 1 > A 2 ,   A 1 > A 5 ,   A 2 > A 5 ,   A 3 > A 5 , A_1>A_2,\,A_1>A_5,\,A_2>A_5,\,A_3>A_5, A1>A2,A1>A5,A2>A5,A3>A5, A 4 > A 5 A_4>A_5 A4>A5


对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 5 1\le N\le 10^5 1N105

13 13 13 个测试点,其中 1 1 1 为样例,其余性质如下:

测试点 2 2 2 满足 N ≤ 100 N\le 100 N100
测试点 3 ∼ 5 3\sim 5 35 满足 N ≤ 5000 N\le 5000 N5000
测试点 6 ∼ 13 6\sim 13 613 没有额外限制。


出题人:Dhruv Rohatgi

树状数组

f(j)= 将所有长度大于j的头发理成j的逆序对数量。
本题第j行的答案就是f(j)。
f(j)=0
我们计算f(j+1)-f(j)。
i<j,x1=a[i],x2=a[j]。
情况一:x1x2,将j+1理成j前后,都不是逆序对。
情况二:x1 < x2 ,无论如何逆序对不变。
情况三:x1 > x2。如果x1
j+1,x2=x。逆序对减少1。其它情况逆序对不变。
f(j+1)-f(j)= 数对(j+1,j)的数量。
由于大于j的头发此时长度都为j+1,故f(j+1)-f(j)=原始x2为j的逆数对数量。
b[j]记录原始数组中x2位i的数量。b的长度N+1,避免溢出。

代码

单元测试

#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>

#include <bitset>
using namespace std;

template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
	in >> pr.first >> pr.second;
	return in;
}

template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) ;
	return in;
}

template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
	return in;
}

template<class T = int>
vector<T> Read() {
	int n;
	scanf("%d", &n);
	vector<T> ret(n);
	for(int i=0;i < n ;i++) {
		cin >> ret[i];
	}
	return ret;
}

template<class T = int>
vector<T> Read(int n) {
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}

template<int N = 12 * 1'000'000>
class COutBuff
{
public:
	COutBuff() {
		m_p = puffer;
	}
	template<class T>
	void write(T x) {
		int num[28], sp = 0;
		if (x < 0)
			*m_p++ = '-', x = -x;

		if (!x)
			*m_p++ = 48;

		while (x)
			num[++sp] = x % 10, x /= 10;

		while (sp)
			*m_p++ = num[sp--] + 48;
	}
	inline void write(char ch)
	{
		*m_p++ = ch;
	}
	inline void ToFile() {
		fwrite(puffer, 1, m_p - puffer, stdout);
	}
private:
	char  puffer[N], * m_p;
};

template<int N = 12 * 1'000'000>
class CInBuff
{
public:
	inline CInBuff() {
		fread(buffer, 1, N, stdin);
	}
	inline int Read() {
		int x(0), f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		return f ? -x : x;
	}
private:
	char buffer[N], * S = buffer;
};


template<class ELE = int >
class CTreeArr
{
public:
	CTreeArr(int iSize) :m_vData(iSize + 1)
	{

	}
	void Add(int index, ELE value)
	{
		if ((index < 0) || (index >= m_vData.size() - 1)) { return; }
		index++;
		while (index < m_vData.size())
		{
			m_vData[index] += value;
			index += index & (-index);
		}
	}
	ELE Sum(int index)//[0...index]之和
	{
		index++;
		ELE ret = 0;
		while (index)
		{
			ret += m_vData[index];
			index -= index & (-index);
		}
		return ret;
	}
	ELE Sum() { return Sum(m_vData.size() - 2); }
	ELE Get(int index)
	{
		return Sum(index) - Sum(index - 1);
	}
private:
	vector<ELE> m_vData;
};

class Solution {
		public:
			vector<long long> Ans(vector<int> a) {
				const int N = a.size();
				CTreeArr<long long> tree(N + 1);
				vector<long long> b(N + 1);
				for (int i = 0; i < N; i++) {
					auto cnt = i - tree.Sum(a[i]);
					b[a[i]] += cnt;
					tree.Add(a[i], 1);
				}
				vector<long long> ans = { 0 };
				for (int i = 1; i < N; i++) {
					ans.emplace_back(ans.back() + b[i-1]);
				}
				return ans;
			}
		};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	int n;
	cin  >> n;	
	auto a = Read<int>(n);
	auto res = Solution().Ans(a);
	for (const auto& i : res)
	{
		cout << i << endl;
	}
#ifdef _DEBUG	
	/*Out(a, ",a=");
	printf("k=%d", k);
	*/
#endif // DEBUG	
	return 0;
}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步优快云学院,听白银讲师(也就是鄙人)的讲解。
https://edu.youkuaiyun.com/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.youkuaiyun.com/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

### 解决方案 USACO 的题目 **P2895 Meteor Shower S** 是一道经典的 BFS(广度优先搜索)问题,涉及路径规划以及动态障碍物的处理。以下是关于此题目的 C++ 实现方法及相关讨论。 #### 1. 题目概述 贝茜需要在一个二维网格上移动到尽可能远的位置,同时避开由流星造成的破坏区域。每颗流星会在特定时间落在某个位置,并摧毁其周围的五个单元格(中心及其上下左右)。目标是最小化贝茜受到的风险并计算最短到达安全地点的时间[^5]。 --- #### 2. 关键算法思路 为了高效解决这个问题,可以采用以下策略: - 使用 **BFS(广度优先搜索)** 来模拟贝茜可能的行走路线。 - 动态更新地图上的危险区域,确保在每个时刻只考虑有效的威胁。 - 提前预处理所有流星的影响范围,减少冗余计算。 由于直接在每次 BFS 中调用 `boom` 函数可能导致性能瓶颈[^4],因此可以通过优化来降低复杂度。 --- #### 3. 优化建议 为了避免重复标记已知的危险区域,可以在程序初始化阶段完成如下操作: - 创建一个数组记录每个单位时间内哪些坐标会被流星影响。 - 将 BFS 和流星爆炸事件同步进行,仅在必要时扩展新的状态。 这种方法能够显著提升运行速度,尤其对于大规模输入数据(如 $ M \leq 50,000 $),效果尤为明显。 --- #### 4. C++ 示例代码实现 下面提供了一个高效的解决方案框架: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 1e6; int grid[1001][1001]; // 地图大小假设为合理范围内 bool visited[1001][1001]; queue<pair<int, pair<int, int>>> q; // 存储 {time, {x, y}} // 方向向量定义 vector<pair<int, int>> directions = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} }; void initializeGrid(int N, vector<tuple<int, int, int>>& meteors) { memset(grid, 0, sizeof(grid)); for(auto &[t, x, y] : meteors){ if(t >= N || t < 0) continue; // 超过最大时间或负数忽略 grid[x][y] = max(grid[x][y], t); for(auto &[dx, dy] : directions){ int nx = x + dx, ny = y + dy; if(nx >=0 && nx <1001 && ny>=0 && ny<1001){ grid[nx][ny] = max(grid[nx][ny], t); } } } } bool isValid(int time, int x, int y){ return !(grid[x][y] <= time); // 如果当前时间<=流星爆炸时间则不可通过 } int main(){ ios::sync_with_stdio(false); cin.tie(0); int T, X, Y; cin >> T >> X >> Y; vector<tuple<int, int, int>> meteors(T); for(int i=0;i<T;i++) cin >> get<0>(meteors[i]) >> get<1>(meteors[i]) >> get<2>(meteors[i]); initializeGrid(X*Y, meteors); memset(visited, false, sizeof(visited)); q.push({0,{X,Y}}); visited[X][Y]=true; while(!q.empty()){ auto current = q.front(); q.pop(); int currentTime = current.first; int cx = current.second.first, cy = current.second.second; if(isValid(currentTime,cx,cy)){ cout << currentTime; return 0; } for(auto &[dx,dy]:directions){ int nx=cx+dx,ny=cy+dy; if(nx>=0&&nx<1001&&ny>=0&&ny<1001&&!visited[nx][ny]){ if(isValid(currentTime,nx,ny)){ q.push({currentTime+1,{nx,ny}}); visited[nx][ny]=true; } } } } cout << "-1"; // 若无解返回-1 return 0; } ``` 上述代码实现了基于 BFS 的最优路径查找逻辑,并预先构建了流星影响的地图以加速查询过程。 --- #### 5. 进一步讨论 尽管本题的核心在于 BFS 及动态更新机制的应用,但在实际编码过程中仍需注意以下几个方面: - 输入规模较大时应选用快速 IO 方法(如关闭同步流 `ios::sync_with_stdio(false)` 并取消绑定 `cin.tie(NULL)`)。 - 对于超出边界或者无关紧要的数据点可以直接跳过处理,从而节省不必要的运算开销。 - 利用位掩码或其他压缩技术存储访问标志可进一步节约内存资源。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软件架构师何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值