【NOIP2013】【Luogu1966】火柴排队(逆序对)
problem
- 给你两个长为n的序列
- 最小化的值,答案%99999997。
solution
一、排序不等式
- 展开:。
- ai^2和bi^2的值是不会变的,所以只能最小化“-∑2*ai*b”。
a<b c<d
根据以上猜测,则ac+bd一定是最大的。利用反证法。
若ac+bd不是最大的,那么一定有比它更大的,只有ad+bc
ac+bd<ad+bc
ac-ad<bc-bd
a*(c-d)<b*(c-d)//c-d<0
a>b(???)
得出矛盾。所以从最简单的情况推出_b数组中第i小的数和a数组中第i小的数在同一个位置_就是最优的。
那么问题就转换成了:如何通过最小的变换使两个序列中每个数在序列中的大小排名都相同。
二、逆序对问题
- 交换a[i]任意个值,使a与b数组同大小对应(如果B数组从小到大已经有序的话,那么答案就是a数组的逆序对,即将a数组从小到大排序需要的交换次数
- 县排序,就得到了a与b数组的位置的对应关系(然而也失去了下标和交换的可能性,所以排序前我们先把下标保存下来
- 我们令c[a[i].num]=b[i].num,使c有序的逆序对个数即为所求(排序后,a[i].num,b[i].nunm都是一个全排列。
- 证明:我们要让a,b对应有序,即要让a中第3大的元素移动到b中第3大元素所在的对应位置。先令a中第3大的元素下标等于b中第3大的元素下标,“比如a中第3大元素下标为2,b中第3大元素下标为5,那么c[2]=5”。(最后的结果应该是c[2]=2,最后一定是有序的全排列,所以对C进行排序要的要换次数就是题目所求,即变成了求c的逆序对个数
- 至于逆序对,归并或者树状数组都可以。
三,逆序对求法
- 这里的逆序对比较特殊,因为是全排列,数值范围不大, 所以提供一种比较好理解的求法。
- 用d[i]保存数值val在序列c中出现的次数,那么数组d[i]在[l,r]上的区间和,就表示序列c在范围[l,r]内的数有多少个。
我们在序列c的数值范围上建立一个树状数组,维护d的前缀和。
倒序扫描序列c,对于每个数c[i]
1、查询[1,c[i]-1]的前缀和(即c[i]后面的数中有多少个比他小),累加到答案中去。
2、执行单点修改,把位置c[i]加1(表示c[i]又出现了一次)
最后,记得要mod一下。
codes
#include<iostream>
#include<algorithm>
#define maxn 1000010
#define mod 99999997
using namespace std;
int c[maxn];
struct node{int val, num;}a[maxn], b[maxn];
bool cmp(node a, node b){return a.val<b.val;}
int n, d[maxn];
void add(int x, int v){ for(int i = x; i <= n; i+=i&(-i))d[i]+=v,d[i]%=mod;}
int query(int x){ int ans=0;for(int i=x;i>0;i-=i&(-i))ans+=d[i],ans%=mod;return ans;}
int main(){
cin>>n;
for(int i = 1; i <= n; i++)
cin>>a[i].val,a[i].num=i;
for(int i = 1; i <= n; i++)
cin>>b[i].val,b[i].num=i;
sort(a+1,a+n+1,cmp);
sort(b+1,b+n+1,cmp);
for(int i = 1; i <= n; i++)
c[a[i].num]=b[i].num;
int ans = 0;
for(int i = n; i >= 1; i--){
add(c[i],1);
ans += query(c[i]-1);
ans %= mod;
}
cout<<ans<<'\n';
return 0;
}