当前位置: 代码迷 >> 综合 >> bzoj 5181: [Baltic2016]Swap
  详细解决方案

bzoj 5181: [Baltic2016]Swap

热度:98   发布时间:2023-10-29 08:19:25.0

题意

给定一个长度为n的序列Xn,该序列中没有重复的数字。你有n-1次操作的机会:在每次操作机会时对应的有着k=2,3,4,…n,(在第1次交换机会时k=2,以此类推)你可以交换X[k]和X[k/2],也可以不进行操作。求在执行完操作后,最小字典序的序列。

前言

这题搞了一天QAQ
主要是不知道怎么做的时候题解找了很久。。
最后是看代码看懂的

一个一开始的想法

我们直接贪心啊
对于三个位置a,b,ca,b,c
分开情况讨论
主要就是尽量要小的放在前面
但这样显然是错的
因为放在某个位置,在后面可能会被换到更后面去

另一个想法

贪心玩了一会都不会。。
于是就考虑另外一个思路
就是如果我们可以对于每一个位置,维护一个他可以填的数的集合,那么也可以贪心
我是想边出答案,别维护
一开始我想的是线段树合并
但是这样是不行的
因为我们在要删除一个节点的时候,如果你把所有包含这个节点的数都删去这个节点
复杂度显然不可以接受
因为,在若干次变化之后,你根本不知道那些有这个数字,你还要把树都遍历一次

同时,这个做法有一个问题,就是没有用到每一次可以交换的地方是固定的
没有利用好性质的做法肯定不是一个优秀的做法

于是就有了更好的做法

但是这个思路还是行得通的
问题在于有一个数被选中的时候,我们怎么样快速地在所有点里面删除这个数
做法是,我们对于每一个点,建立二叉树
然后对于每一个交换操作,都建立一个新的点
两个状态分别表示换和不换
显然地,你两个操作只能选择一个
也就是说,你只可以选择一个
于是对于每一个交换节点,加一个标记就可以了
然后我们就可以贪心了
每一次遍历一棵树,然后再暴力删去他
考虑复杂度,每一个数的大小是
T(n)=T(n/2)+2T(n)=T(n/2)+2
因此是loglog级别的
暴力添加不会T
同时很好地利用了交换的数是x,x/2x,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;
}
  相关解决方案