题意:小朋友排队,从低到高,每次只能交换相邻的两个人,每换一次,小朋友就会有不高兴值的累加,不高兴程度每次加上其交换的次数,问小朋友不高兴程度的最小值是多少
思路
找逆序对
发现,当一个数左右存在非升序情况(如左边的数比当前大,右边小)这样的数是成对出现的,同时一旦出现就意味着一定要跨越当前数来形成正确的排序。 那找到所有逆序的数就找到了所有要交换的数,再统计一下逆序的数量,等差求和得到最终解。
从代码来看,思路是贪心的:即每次找到逆序就交换,换到不逆序为止,找下一个逆序。
证明:缺乏严格证明,不知道为什么这样最小 只能认为最佳的交换策略可以实现不产生额外的逆序情况,每次交换都可以消除某个逆序。 怎么找?
归并可以对每个区间分到最小,即每次对相邻的两个区间查找,而且区间左右关系确定,可以通过归排选中了哪一个数来判定其左边与右边的逆序情况。
实现
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
typedef pair<LL, LL> PII;
PII p[N];
//int cnt[N];
PII t[N];
void merge(int l, int r)
{
if(l >= r) return;
int mid = l + r >> 1;
merge(l, mid); merge(mid + 1, r);
int i = l, j = mid + 1, k = 0;
while(i <= mid && j <= r)
{
if(p[i].first <= p[j].first)
{
//i选中了,则j之前所有数都比i小,理应比i大,是逆序
p[i].second += j - mid - 1;
t[k ++] = p[i ++];
}
else
{
//j选中了,则i之后的数比j大,理应比j小
p[j].second += mid - i + 1;
t[k ++] = p[j ++];
}
}
//如果j先走完了,说明顺序不对
while(i <= mid)
{
p[i].second += j - mid - 1;
t[k ++] = p[i ++];
}
//i先走完说明顺序是对的
while(j <= r) t[k ++] = p[j ++];
for(int i = l, k = 0; i <= r; i ++, k ++)
p[i] = t[k];
}
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i ++)
{
cin >> p[i].first;
//p[i].second = i;
}
merge(0, n-1);
LL ans = 0;
for(int i = 0; i < n; i ++)
{
ans += p[i].second * (p[i].second + 1) / 2;
}
cout << ans << endl;
return 0;
}