题意简述
给定一个序列aaa,长度为n(<=1e5)n(<=1e5)n(<=1e5),你珂以交换序列中的两个数,使得序列满足:对于每个点,要么它>=>=>=所有左边的元素,要么它>=>=>=所有右边的元素。(形象的说,就是一个山峰)输出最少的交换次数。记得开longlonglonglonglonglong。
思路
倒序排序,一个一个插入,判断是插入在左边还是插入在右边,记录逆序对个数,求出答案
具体思路
这个问题看起来不好直接求。确实,这种题如果第一次做的话,是很难想到做法的。
首先确定这样一个东西:交换次数=把原序列看成{1,2,3...n}\{1,2,3...n\}{1,2,3...n}之后交换得到的逆序对数。
然后我们要让这个值最小。接下来就是如何求这个最小了。
有这样一个模型,像这种要么左边,要么右边的题目,我们珂以这样考虑:
- 将aaa序列倒序
- 初始序列为空
- 考虑我们新加入的元素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,i−1]中满足pj<pip_j<p_ipj<pi的jjj的个数。同理,插入在最右边的代价就是j∈[1,i−1]j\in [1,i-1]j∈[1,i−1]中满足pj>pip_j>p_ipj>pi的jjj的个数。第一个很明显珂以用树状数组记录,设答案为sss。那么第二个就不用再去记录了,就是i−1−xi-1-xi−1−x。i−1i-1i−1是数字的总数,由于pip_ipi显然不会有相等的情况,所以总数-小于的就是大于的。
然后比较一下是sss大还是i−1−si-1-si−1−s大即珂,记录到答案。
注意,如果有连续的一段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

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

被折叠的 条评论
为什么被折叠?



