IOI 2019 排列鞋子 题解

题目传送门

题目大意: n n n 对鞋子,每只鞋子有一个大小,大小互为相反数的可以配成一对,现在需要将鞋子重新排列,使得第 2 i 2i 2i 位上的鞋子和第 2 i − 1 2i-1 2i1 位上的鞋子成一对,并且 2 i − 1 2i-1 2i1 位上的鞋子的大小要是负数,问从鞋子的原始排列移动到目标排列至少需要几次移动。

题解

先考虑将鞋子配对。

从左往右遍历一遍,对于大小为 x ( x > 0 ) x(x>0) x(x>0) 的鞋子,可以将它与序列中最左边的大小为 − x -x x 的鞋子配对,这样的话移动次数一定是最优的。

证明很显然,这种选的方法是为了避免出现下面这类沙雕配对
在这里插入图片描述
那么配完对之后,究竟是 − x -x x x x x 那边跑,还是 x x x − x -x x 那边跑呢?

先搞一个 b b b 数组, b [ i ] b[i] b[i] 表示和 i i i 配对的那个鞋子的位置。

然后从左到右遍历一遍,假如当前位置 x x x 还没有被操作过,那么就让 b [ x ] b[x] b[x] 跑过来,跑到 x x x 的旁边。(有点像 x x x b [ x ] b[x] b[x] 拉过来那个样子)那么需要交换的次数就是 b [ x ] − x b[x]-x b[x]x

遍历完一遍,所有的鞋子的旁边都是和自己一对的了。

那么为什么让 b [ x ] b[x] b[x] 跑过来一定是最优的呢?

显然,对于每个没有操作过的位置,如果将它的 b [ x ] b[x] b[x] 给拉过来,那么 x x x b [ x ] b[x] b[x] 就一定不会出现在别的鞋子对中间了,那么也就缩短了别的鞋子对之间的距离。但如果 x x x 屁颠屁颠的跑过去凑到 b [ x ] b[x] b[x] 旁边,那么很可能就会使得别的鞋子对在移动的时候需要和他们两个进行交换了。

总而言之,总的思路也就是两个贪心。

再扯一扯代码的实现。

对于大小为负数的鞋子,将大小相同的用链表连起来。

对于 x x x b [ x ] b[x] b[x] 拉过来那个操作,显然是不现实的,我们只需要记录每个点是否被操作过,然后 x x x b [ x ] b[x] b[x] 中间没有被操作过的点数就是移动次数,然后将 x x x b [ x ] b[x] b[x] 标记为操作过,这个可以用树状数组来搞。

代码如下:

#include <cstdio>
#include <cstring>
#define maxn 200010
#define ll long long

struct node{
	int x;//这个鞋子的位置
	node *next;//下一个大小相同的鞋子
	node(int y):x(y),next(NULL){};
};
node *head[maxn],*tail[maxn];//记录下每个大小的鞋子的链表的链首和链尾
int n,a[maxn],b[maxn],tr[maxn];
inline int lowbit(int x){return x&(-x);}//树状数组实现部分
void add(int x,int y)
{
	for(;x<=n;x+=lowbit(x))
	tr[x]+=y;
}
int sum(int x)
{
	int re=0;
	for(;x>=1;x-=lowbit(x))
	re+=tr[x];
	return re;
}
bool v[maxn];//记录是否有操作过
ll ans=0;

int main()
{
	scanf("%d",&n);n*=2;
	for(int i=1;i<=n;i++)
	head[i]=tail[i]=NULL;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if(a[i]<0)
		{//将负的且大小相同的连在一起
			if(tail[-a[i]]==NULL)head[-a[i]]=tail[-a[i]]=new node(i);//如果这是第一个大小为a[i]的鞋子
			else tail[-a[i]]->next=new node(i),tail[-a[i]]=tail[-a[i]]->next;//如果不是第一个,那么就接在上一个的后面
			//需要注意,a[i]是负数,这里tail和head数组的下标都是-a[i]
		}
	}
	for(int i=1;i<=n;i++)//配对
	{
		if(a[i]>0)
		{
			b[i]=head[a[i]]->x;//找到大小互为相反数的最左边的鞋子
			b[head[a[i]]->x]=i;//相互配对
			if(head[a[i]]->x>i)ans++;//假如负的鞋子在正的鞋子的右边,那么在正的鞋子把负的鞋子拉过来之后,两只鞋子还要进行一次交换,这里把那次交换记录下来
			head[a[i]]=head[a[i]]->next;//更新最左边的值为-a[i]的鞋子的位置
		}
	}
	for(int i=1;i<=n;i++)
	add(i,1);//一开始标记树状数组中每一个位置都没有被操作过,
	for(int i=1;i<=n;i++)
	{
		if(v[i])continue;//假如被操作过就跳过
		ans+=(ll)sum(b[i]-1)-sum(i);//加上两只鞋子之间没有被操作过的鞋子的数量
		add(i,-1);add(b[i],-1);//打标记
		v[i]=v[b[i]]=true;
	}
	printf("%lld",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值