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;
}
总结
犯错没什么,正确的方向就藏在其中。尝试过程中碰壁的次数越来越少,说明这个人还有点东西,进步很快。