Atcoder 1218 bzoj 4240 libreoj 2873 「JOISC 2014 Day1」有趣的家庭菜园 题解

该博客解析了一道编程竞赛题目,涉及到在序列中寻找满足特定条件的最少交换次数。通过倒序排序,逐个插入序列,并计算逆序对数量来找到最小交换次数。博主强调了在实现过程中需要注意的细节,特别是处理相同数值的情况和使用longlong类型避免溢出。

题意简述

给定一个序列aaa,长度为n(<=1e5)n(<=1e5)n(<=1e5),你珂以交换序列中的两个数,使得序列满足:对于每个点,要么它>=>=>=所有左边的元素,要么它>=>=>=所有右边的元素。(形象的说,就是一个山峰)输出最少的交换次数。记得开longlonglonglonglonglong

思路

倒序排序,一个一个插入,判断是插入在左边还是插入在右边,记录逆序对个数,求出答案

具体思路

这个问题看起来不好直接求。确实,这种题如果第一次做的话,是很难想到做法的。

首先确定这样一个东西:交换次数=把原序列看成{1,2,3...n}\{1,2,3...n\}{1,2,3...n}之后交换得到的逆序对数。

然后我们要让这个值最小。接下来就是如何求这个最小了。

有这样一个模型,像这种要么左边,要么右边的题目,我们珂以这样考虑:

  1. aaa序列倒序
  2. 初始序列为空
  3. 考虑我们新加入的元素a[i]a[i]a[i]在最左边还是最右边,插入进去

正确性是显然的。由于我们在插入a[i]a[i]a[i]的时候,所有数都是>=a[i]>=a[i]>=a[i]的。所以我们不管插入在左边还是右边,还是能构成一个“山峰”。只不过是左边长还是右边长的问题了。

我们开一个结构体,把每个数按值(设为viv_ivi)排序,然后还要保留原来的位置(方便统计逆序数,设为pip_ipi)。

那么我们把aia_iai插入在序列的最左边,花费就是新加入的逆序对数,也就是j∈[1,i−1]j\in [1,i-1]j[1,i1]中满足pj<pip_j<p_ipj<pijjj的个数。同理,插入在最右边的代价就是j∈[1,i−1]j\in [1,i-1]j[1,i1]中满足pj>pip_j>p_ipj>pijjj的个数。第一个很明显珂以用树状数组记录,设答案为sss。那么第二个就不用再去记录了,就是i−1−xi-1-xi1xi−1i-1i1是数字的总数,由于pip_ipi显然不会有相等的情况,所以总数-小于的就是大于的。

然后比较一下是sss大还是i−1−si-1-si1s大即珂,记录到答案。

注意,如果有连续的一段aia_iai相等的话,那么要记得判一下。如果不判的话,我们就要记录一些类似“交换两个相同的数”的无用功了,就不一定最小。我们原来那个算法几乎完全是基于数值viv_ivi进行插入判断的,pip_ipi只是起到答案的作用。所以即使是相同的viv_ivi,由于pip_ipi不同,我们也会认为这是不一样的数,然后还会记录答案。这很明显是不必要的。

实现注意

千万不要忘了开longlonglong longlonglong!!!
千万不要忘了开longlonglong longlonglong!!!
千万不要忘了开longlonglong longlonglong!!!
千万不要忘了开longlonglong longlonglong!!!
千万不要忘了开longlonglong longlonglong!!!

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
	#define int long long 
	#define N 355555
	#define F(i,l,r) for(int i=l;i<=r;++i)
	#define D(i,r,l) for(int i=r;i>=l;--i)
	#define Fs(i,l,r,c) for(int i=l;i<=r;c)
	#define Ds(i,r,l,c) for(int i=r;i>=l;c)
	#define Tra(i,u) for(int i=G.Start(u),__v=G.To(i);~i;i=G.Next(i),__v=G.To(i))
	#define MEM(x,a) memset(x,a,sizeof(x))
	#define FK(x) MEM(x,0)
	class BIT
	{
	    public:
	        int tree[N];
	        int len;
	        void BuildTree(int _len)
	        {
	            len=_len;
	            FK(tree);
	        }
	        void Add(int pos,int val=1)
	        {
	            for(int i=pos;i<=len;i+=(i&(-i)))
	            {
	                tree[i]+=val;
	            }
	        }
	        int Query(int pos)
	        {
	            int ans=0;
	            for(int i=pos;i>0;i-=(i&(-i)))
	            {
	                ans+=tree[i];
	            }
	            return ans;
	        }
	}T;

	struct node
	{
		int h,pos;
	}a[N];
	bool operator<(node a,node b){return a.h>b.h;}
	int n;
	void R1(int &x)
	{
	    x=0;char c=getchar();int f=1;
	    while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
	    while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	    x=(f==1)?x:-x;
	}
	void Input()
	{
		R1(n);
		F(i,1,n)
		{
			R1(a[i].h);
			a[i].pos=i;
		}
	}

	void Soviet()
	{
		sort(a+1,a+n+1);
		T.BuildTree(n);

		int j;
		int ans=0;
		F(i,1,n)
		{
			j=i;
			while(a[j].h==a[j+1].h and j<n) ++j;
			F(k,i,j) ans+=min(T.Query(a[k].pos),i-1-T.Query(a[k].pos));
			F(k,i,j) T.Add(a[k].pos);
			i=j;
		}

		printf("%lld\n",ans);
	}
	void IsMyWife()
	{
		Input();
		Soviet();
	}
	#undef int //long long 
}
int main()
{
	Flandre_Scarlet::IsMyWife();
	getchar();getchar();
	return 0;
}2
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值