P1966 [NOIP2013 提高组] 火柴排队

原题链接
考察:树状数组+hash

本题的总结:

  • 如果b可以交换任意元素到a,那么最少交换次数 = 环长度-1
  • 如果b需要交换相邻的到a,那么最少交换次数 = 逆序对数目.

思路:
  这道题如果贪心把每个数列的数字尽可能换成一样的是错误的.例二就是HACK数据.既然这样我们就考虑怎么转化答案式子来求最值.
  本题求\(\sum\)(ai2-bi2)的最小值即是求\(\sum\)ai2+bi2-2*ai*bi的最小值.因为每个i都会枚举到,所以前面的\(\sum\)(ai2+bi2)是一个定值.我们能做的就是最大化2*ai*bi.
  参考下面遗忘的高中数学知识:
image
  所以我们要让离散后的a数组与b数组相等.
  那么怎么快速求出交换次数呢?
image
  再以普通的求逆序对的方法求解即可.

#include <iostream> 
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 100010,Mod = 99999997;
int n,a[N],b[N],c[N],tr[N];
vector<int> A;
int get(int x)
{
	return lower_bound(A.begin(),A.end(),x)-A.begin()+1;
}
int lowbit(int x)
{
	return x&-x;
}
int ask(int x)
{
	int ans = 0;
	for(int i=x;i;i-=lowbit(i)) ans+=tr[i];
	return ans;
}
void add(int k,int x)
{
	for(int i=k;i<=n;i+=lowbit(i)) tr[i]+=x;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		A.push_back(a[i]);
	}
	sort(A.begin(),A.end());
	for(int i=1;i<=n;i++)
	{
		a[i] = get(a[i]);
		c[a[i]] = i;
	}
	A.clear();
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&b[i]);
		A.push_back(b[i]);
	}
	sort(A.begin(),A.end());
	for(int i=1;i<=n;i++)
	{
		int x = get(b[i]);
		b[i] = c[x];//求b的逆序对数量 
	}
	int ans = 0;
	for(int i=1;i<=n;i++)
	{
		ans+=ask(n)-ask(b[i]);
		ans%=Mod;
		add(b[i],1);
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2021-05-15 17:31  acmloser  阅读(64)  评论(0编辑  收藏  举报