修改数组 第十届蓝桥杯省赛C++A/研究生组

AcWing原题链接

Ac code

#include<vector>
#include<cstring>
#include<iostream>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;

#define _for(i,from,to) for(int i=(from);i<(to);++i)//[from,to)
#define _clear(arr,val) fill(arr.begin(), arr.end(), val);
#define _zero(arr) memset(arr,0,sizeof(arr))
#define out(x) cout<<x<<endl
#define _read(x) cin>>x

typedef pair<int, int> PII;
typedef long long ll;
typedef vector<int> Vec;
const int MAXN = 1000002;

int N;
int fa[MAXN];//记录当前组里的最大位置
int vis[MAXN];
int arr[100003];

void init();
int find(int);

int main()
{
	init();

	vis[arr[1]] = 1;
	_for(i, 2, N + 1) {
		int root = find(arr[i]);
		while (vis[root]) {
			fa[root] = find(root + 1);//合并
			root = fa[root];
		}
		arr[i] = root;
		vis[root] = 1;
	}
	
	_for(i, 1, N + 1) {
		cout << arr[i] << " ";
	}

	system("pause");
	return 0;
}

void init() {
	_read(N);
	_for(i, 1, N + 1) {
		_read(arr[i]);
	}
	_for(i, 1, MAXN) {
		fa[i] = i;
	}

}
int find(int i) {
	while (i != fa[i]) {
		fa[i] = fa[fa[i]];
		i = fa[i];
	}
	return i;
}

/*
5
2 1 1 3 4

12
1 1 1 1 1 1 1 1 1 1 1 1

*/

正解

题目大意,有一个正整数数组,可能有重复。
从左向右,如果当前值在前面出现过,就增加一,直到首次出现。

3 2 3 2 --> 3 2 4 5

理解为哈希冲突的过程,把数组元素当成是开始要存放物品的位置,出现重复相当于发生了冲突。

并查集,父亲指针指向自己的存放的位置,查找这个组别的根得到最后一次存放的位置{最大值}。
灵魂画手

每个数初始化为自己,表明没有冲突。

如果当前的n=arr[i]在vis[n]被标记,发生冲突,需要合并n和n+1,直到找到新的未标记位置。反之,直接标记。

差点意思的解法

最直白:模拟加一操作 O(n^2) 通过测试用例{7/11}

//头文件同上
const int MAXN = 1000002;
int N;

int vis[MAXN];
int arr[100003];

int nxt=MAXN;

/////////////////

void init();

int check(int pos);

int main()
{
	init();

	_for(i, 2, N + 1) {
		if (vis[arr[i]]) {
			int addTo = check(arr[i]);
			if (addTo != 0) {
				arr[i] = addTo;
			}
		}
		else {
			vis[arr[i]] = 1;
		}
	}

	_for(i, 1, N + 1) {
		cout << arr[i] << " ";
	}

	system("pause");
	return 0;
}

void init() {
	_read(N);

	int t;
	_for(i, 1, N + 1) {
		_read(t);
		arr[i] = t;
		nxt = min(nxt, t);
	}

	vis[arr[1]] = 1;

}
int check(int pos) {
	int ans = 0;
	bool used = false;

	if (pos < nxt) {
		used = true;
		pos = nxt;
	}

	_for(i, pos, MAXN) {
		if (!vis[i]) {
			ans = i;
			vis[i] = 1;
			if (used) {
				nxt = i + 1;
			}
			break;
		}
	}

	return ans;
}

说明:

直接模拟一定超时,引入nxt记录上一次存放的位置,但只对数据比较密集有效(快速形成一个组),但就像正解体现的,这里是多个组别,一个指针远远不够。所以,试一试多个指针,为每一个位置存放一个最后位置。

再改进:利用位置数组,通过用例{10/11}

const int MAXN = 1000002;

int N;
int pos[MAXN];//记录i冲突后的位置
int arr[100003];

/////////////////

void init();

int check(int pos);

int main()
{
	init();

	_for(i, 2, N + 1) {
		int old = arr[i];
		if (pos[old]) {
			arr[i] = check(old);
		}
		pos[old] =arr[i];//更新旧位置
	}

	_for(i, 1, N + 1) {
		cout << arr[i] << " ";
	}

	system("pause");
	return 0;
}

void init() {
	_read(N);

	int t;
	_for(i, 1, N + 1) {
		_read(t);
		arr[i] = t;
	}

	pos[arr[1]] = arr[1];
	

}
int check(int p) {
	int ans = 0;//pos所在位置一定用过了

	if (pos[p]) {
		p = pos[p]+1;
	}//如果发生冲突,从上一次位置开始

	for (int i = p; i < MAXN;) {
		if (!pos[i]) {
			ans = i;
			pos[i] = i;//更新初次出现
			break;
		}
		else {
			i = pos[i] + 1;
		}
		
	}

	return ans;
}

说明:

此时已经有了并查集的雏形,但是少了一个折叠路径的操作,你虽然记录了每个点的冲突位置,但每次查找只是看一眼,一步一步走,还是差点意思。

和正解差了一个查找函数,把一步一跳,变成一步多跳。如果能一步到位才是打表法根本性的优势,花了不少空间没有突破性的进步,说明没有充分发挥数组坐标直达的优势。

int find(int i) {
	while (i != fa[i]) {
		fa[i] = fa[fa[i]];//折叠路径
		i = fa[i];
	}
	return i;
}

总结

犯错没什么,正确的方向就藏在其中。尝试过程中碰壁的次数越来越少,说明这个人还有点东西,进步很快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值