题意
给定一个长度为n的序列Xn,该序列中没有重复的数字。你有n-1次操作的机会:在每次操作机会时对应的有着k=2,3,4,…n,(在第1次交换机会时k=2,以此类推)你可以交换X[k]和X[k/2],也可以不进行操作。求在执行完操作后,最小字典序的序列。
前言
这题搞了一天QAQ
主要是不知道怎么做的时候题解找了很久。。
最后是看代码看懂的
一个一开始的想法
我们直接贪心啊
对于三个位置
a,b,c
a
,
b
,
c
分开情况讨论
主要就是尽量要小的放在前面
但这样显然是错的
因为放在某个位置,在后面可能会被换到更后面去
另一个想法
贪心玩了一会都不会。。
于是就考虑另外一个思路
就是如果我们可以对于每一个位置,维护一个他可以填的数的集合,那么也可以贪心
我是想边出答案,别维护
一开始我想的是线段树合并
但是这样是不行的
因为我们在要删除一个节点的时候,如果你把所有包含这个节点的数都删去这个节点
复杂度显然不可以接受
因为,在若干次变化之后,你根本不知道那些有这个数字,你还要把树都遍历一次
同时,这个做法有一个问题,就是没有用到每一次可以交换的地方是固定的
没有利用好性质的做法肯定不是一个优秀的做法
于是就有了更好的做法
但是这个思路还是行得通的
问题在于有一个数被选中的时候,我们怎么样快速地在所有点里面删除这个数
做法是,我们对于每一个点,建立二叉树
然后对于每一个交换操作,都建立一个新的点
两个状态分别表示换和不换
显然地,你两个操作只能选择一个
也就是说,你只可以选择一个
于是对于每一个交换节点,加一个标记就可以了
然后我们就可以贪心了
每一次遍历一棵树,然后再暴力删去他
考虑复杂度,每一个数的大小是
T(n)=T(n/2)+2
T
(
n
)
=
T
(
n
/
2
)
+
2
因此是
log
l
o
g
级别的
暴力添加不会T
同时很好地利用了交换的数是
x,x/2
x
,
x
/
2
这个条件
CODE:
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
const int N=200005*5;
struct qq
{
int s1,s2;
int val;
bool d;//是否确定了儿子
}tr[N];
int n;
int rt[N];
int tot;
int Query (int x)
{
if (tr[x].val!=0) return tr[x].val;
if (tr[x].d==true)
return Query(tr[x].s1);
else return min(Query(tr[x].s1),Query(tr[x].s2));
}
bool Del (int x,int val)
{
if (tr[x].val!=0)
{
if (tr[x].val==val)
{
tr[x].val=n+1;
return true;
}
return false;
}
if (tr[x].d==true) return Del(tr[x].s1,val);
else
{
if (Del(tr[x].s1,val))
{
tr[x].d=true;
return true;
}
else if (Del(tr[x].s2,val))
{
tr[x].d=true;
swap(tr[x].s1,tr[x].s2);
return true;
}
return false;
}
}
int main()
{
scanf("%d",&n);
for (int u=1;u<=n;u++)
{
int x;
scanf("%d",&x);
tot++;
tr[tot].val=x;
rt[u]=tot;
}
for (int u=2;u<=n;u++)
{
int a=rt[u/2],b=rt[u];
rt[u/2]=++tot;tr[tot].s1=a;tr[tot].s2=b;tr[tot].d=false;
rt[u]=++tot;tr[tot].s1=a;tr[tot].s2=b;tr[tot].d=false;
}
for (int u=1;u<=n;u++)
{
int x=Query(rt[u]);
Del(rt[u],x);
if (u!=1) printf(" ");
printf("%d",x);
}
return 0;
}