FHQ-Treap神秘错误(玄关)
查看原帖
FHQ-Treap神秘错误(玄关)
967074
__FL__楼主2025/2/6 15:33

一开始我写了如下0pts代码,大体思路是每次利用getrnk函数查找序列中最小的数的位置,然后利用懒标记g进行翻转操作,最后将第一个数(最小的数)删除。

#include<bits/stdc++.h>
using namespace std;
int n,m,a[200005],l,r,cnt,root,g[200005];
pair<int,int>b[200005];
string op;
struct node
{
	int val,rnd,ls,rs,sz,mn,mp,fa;
}tr[200005];
inline int getnode(int v) {tr[++cnt] = {v,rand(),0,0,1,v,cnt,0}; return cnt;}
inline void pushup(int root)
{
	int ls = tr[root].ls;
	int rs = tr[root].rs;
	tr[root].sz = tr[ls].sz+tr[rs].sz+1;
	tr[root].mn = tr[root].val;
	tr[root].mp = root;
	if (ls)
	{
		tr[ls].fa = root;
		if (tr[ls].mn < tr[root].mn) tr[root].mn = tr[ls].mn,tr[root].mp = tr[ls].mp;
	}
	if (rs)
	{
		tr[rs].fa = root;
		if (tr[rs].mn < tr[root].mn) tr[root].mn = tr[rs].mn,tr[root].mp = tr[rs].mp;
	}
}
inline void pushdown(int root)
{
	swap(tr[root].ls,tr[root].rs); 
	g[root] = 0;
	g[tr[root].ls] ^= 1;
	g[tr[root].rs] ^= 1;
}
void split(int root,int v,int &x,int &y)
{
	if (!root)
	{
		x = 0,y = 0;
		return;
	}
	if (g[root]) pushdown(root);
	if (tr[tr[root].ls].sz < v)
	{
		x = root;
		split(tr[root].rs,v-tr[tr[root].ls].sz-1,tr[root].rs,y);
		pushup(root);
	}
	else
	{
		y = root;
		split(tr[root].ls,v,x,tr[root].ls);
		pushup(root);
	}
}
int merge(int x,int y)
{
	if (!x || !y) return x+y;
	if (g[x]) pushdown(x);
	if (g[y]) pushdown(y);
	if (tr[x].rnd <= tr[y].rnd)
	{
		tr[x].rs = merge(tr[x].rs,y);
		pushup(x);
		return x;
	}
	else
	{
		tr[y].ls = merge(x,tr[y].ls);
		pushup(y);
		return y;
	}
}
void insert(int v)
{
	int z = getnode(v);
	root = merge(root,z);
}/*
void prtr(int u)
{
	if (g[u]) pushdown(u);
	if (tr[u].ls) prtr(tr[u].ls);
	cout << u << ' ';
	if (tr[u].rs) prtr(tr[u].rs);
}
void prt(int u)
{
	if (g[u]) pushdown(u);
	if (tr[u].ls) prt(tr[u].ls);
	cout << tr[u].val << ' ';
	if (tr[u].rs) prt(tr[u].rs);
}*/
int getrnk(int u)
{
	int ret = 1+tr[tr[u].ls].sz;
	for (; tr[u].fa && (tr[tr[u].fa].ls == u || tr[tr[u].fa].rs == u); u = tr[u].fa)
	{
		if (tr[tr[u].fa].rs == u)
		{
			ret += tr[tr[tr[u].fa].ls].sz+1;
		}
	}
	return ret;
}
int query()
{
//	prtr(y);
//	cout << '\n';
//	prt(y);
//	cout << '\n';
	int ret = getrnk(tr[root].mp);
	return ret;
}
signed main()
{
//	ios_base::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> b[i].first;
		b[i].second = i;
	}
	sort(b+1,b+n+1);
	for (int i = 1; i <= n; i++)
		a[b[i].second] = i;
	for (int i = 1; i <= n; i++)
		insert(a[i]);
	for (int i = 1; i <= n; i++)
	{
		int q = query();
		cout << q+i-1 << ' ';
		int x,o,y,z;
		split(root,q,x,y);
		g[x] ^= 1;
		split(x,1,o,z);
		root = merge(z,y);
		//prt(root);
		//cout << '\n' << '\n';
	}
    return 0;
}

经过对拍和测试,发现是因为g数组中存放的懒标记没有及时下放导致错误,理由是如果每次执行如prt这样的携带更新懒标记的测试函数,答案会恢复正确。

Hack样例:

6
4 2 6 5 3 1

多次修改未果后我写出了如下代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,a[200005],l,r,cnt,root,g[200005];
pair<int,int>b[200005];
string op;
struct node
{
	int val,rnd,ls,rs,sz,mn,mp,fa;
}tr[200005];
inline int getnode(int v) {tr[++cnt] = {v,rand(),0,0,1,v,cnt,0}; return cnt;}
inline void pushdown(int root)
{
	swap(tr[root].ls,tr[root].rs);
	g[root] = 0;
	g[tr[root].ls] ^= 1;
	g[tr[root].rs] ^= 1;
}
inline void pushup(int root)
{
	int ls = tr[root].ls;
	int rs = tr[root].rs;
	tr[root].sz = tr[ls].sz+tr[rs].sz+1;
	tr[root].mn = tr[root].val;
	tr[root].mp = root;
	if (ls)
	{
		tr[ls].fa = root;
		if (tr[ls].mn < tr[root].mn) tr[root].mn = tr[ls].mn,tr[root].mp = tr[ls].mp;
	}
	if (rs)
	{
		tr[rs].fa = root;
		if (tr[rs].mn < tr[root].mn) tr[root].mn = tr[rs].mn,tr[root].mp = tr[rs].mp;
	}
}
void split(int root,int v,int &x,int &y)
{
	if (!root)
	{
		x = 0,y = 0;
		return;
	}
	if (g[root]) pushdown(root);
	if (tr[tr[root].ls].sz < v)
	{
		x = root;
		split(tr[root].rs,v-tr[tr[root].ls].sz-1,tr[root].rs,y);
		pushup(root);
	}
	else
	{
		y = root;
		split(tr[root].ls,v,x,tr[root].ls);
		pushup(root);
	}
}
int merge(int x,int y)
{
	if (!x || !y) return x+y;
	if (g[x]) pushdown(x);
	if (g[y]) pushdown(y);
	if (tr[x].rnd <= tr[y].rnd)
	{
		tr[x].rs = merge(tr[x].rs,y);
		pushup(x);
		return x;
	}
	else
	{
		tr[y].ls = merge(x,tr[y].ls);
		pushup(y);
		return y;
	}
}
void insert(int v)
{
	int z = getnode(v);
	root = merge(root,z);
}
void prtr(int u)
{
	if (g[u]) pushdown(u);
	if (tr[u].ls) prtr(tr[u].ls);
	cout << u << ' ';
	if (tr[u].rs) prtr(tr[u].rs);
}
void prt(int u)
{
	if (tr[u].ls) prt(tr[u].ls);
	cout << tr[u].val << ' ';
	if (tr[u].rs) prt(tr[u].rs);
}
int getrnk(int u)
{
	int ret = 1+tr[tr[u].ls].sz;
	stack<int>s;
	for (int o = u; o; o = tr[o].fa)
		s.push(o);
	while (!s.empty())
	{
		int now = s.top();
		s.pop();
		if (s.empty()) break;
		if (g[now]) pushdown(now);
		if (tr[now].rs == s.top())
			ret += 1+tr[tr[now].ls].sz;
	}
	return ret;
}
int query()
{
	int ret = getrnk(tr[root].mp);

	int x,y; //增加部分
	split(root,ret,x,y); //+
	root = merge(x,y); // +
	ret = getrnk(tr[root].mp); //+

	return ret;
}
signed main()
{
	ios_base::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> b[i].first;
		b[i].second = i;
	}
	sort(b+1,b+n+1);
	for (int i = 1; i <= n; i++)
		a[b[i].second] = i;
	for (int i = 1; i <= n; i++)
		insert(a[i]);
	for (int i = 1; i <= n; i++)
	{
		int q = query();
		cout << q+i-1 << ' ';
		int x,o,y,z;
		split(root,q,x,y);
		g[x] ^= 1;
		split(x,1,o,z);
		root = merge(z,y);
	}
    return 0;
}

形象地描述修改部分,就是将序列掰开再合上,来更新懒标记g数组。Accepted。

但是我不明白为什么会出现这样的错误,我也不明白这种修改方式是否完全正确。故询问。

2025/2/6 15:33
加载中...