您的位置:首页 > 其它

NOIP 2013 Senior 2 - 火柴排队

2017-07-20 10:11 190 查看
涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。现在将每盒中的火柴各自排成一列,同一列火柴的高度互不相 同,两列火柴之间的距离定义为: ,其中 ai表示第一列火柴中第 i 个火柴的高度,bi表示第二列火柴中第 i 个火柴的高度。

每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要 交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997 取模的结果。

Input

共三行,第一行包含一个整数 n,表示每盒中火柴的数目。

第二行有 n 个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。

第三行有 n 个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度。

Output

输出共一行,包含一个整数,表示最少交换次数对 99,999,997 取模的结果。

Sample Input

输入1:

 4

2 3 1 4

3 2 1 4

输入2:

 4

1 3 4 2

1 7 2 4

Sample Output

输出1:

1

样例1说明:

最小距离是 0,最少需要交换 1 次,比如:交换第 1 列的前 2 根火柴或者交换第 2 列的前 2 根火柴。

输出2:

2

样例2说明:

最小距离是 10,最少需要交换 2 次,比如:交换第 1 列的中间 2 根火柴的位置,再交换第 2 列中后 2 根火柴的位置。

Data Constraint

对于 10%的数据, 1 ≤ n ≤ 10;

对于 30%的数据,1 ≤ n ≤ 100;

对于 60%的数据,1 ≤ n ≤ 1,000;

对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤火柴高度≤ 2^31 − 1。

首先,我们看数据规模,发现这个数据规模适合O(nlogn)的算法。然后读题,发现要求的是交换相邻元素的最小次数,同时同一列中不存在相同高度的火柴,由此可知此题跟求序列的逆序数有关。

发现,如果可以交换其中一列火柴,那么交换另一列火柴也可以用相同的次数达到同样的效果,因此我们可以只操作一列。现在的问题就是,怎样的最终状态是最佳的。

观察D=Σni=1(ai+bi)2,发现原式可以展开变为D=Σni=1(a2i+b2i)−2∗Σni=1(ai∗bi)。

因此相当于要使Σni=1(ai∗bi)最大,这时可以使用排序不等式。即正序和(ai∗bj的和)>乱序和>逆序和,因此只需要让两列火柴中每对火柴排序后对应的序号相等就可以了。

为了防止间接排序出现问题,这里使用直接排序:

struct info
{
INT height;
INT index;
INT sortIndex;
} infos[2][maxn];


首先,根据
height
关键字进行排序,然后记录每根火柴排序后的序号
sortIndex
,再根据原序号
index
排序还原序列。然后,用第一列火柴的数据记录每个排序后的序号应该放的位置。用这个“位置序号”去替换第二列的
sortIndex
得到一个新序列。这个新序列的逆序数便是答案。

例如,两列火柴为

1 3 2 4
7 2 5 4


它们对应的
sortIndex


1 3 2 4
4 1 3 2


则1该放的位置为1,2该放的位置为3,3该放的位置为2,4该放的位置为4。

用该放的位置去替换排序序号

//第二列
4 1 2 3


最后求出这个序列的逆序数便是答案。

现在的问题就成了,如何求逆序数。逆序数可以使用老生常谈的归并排序来求,在考试时我也使用的是归并排序。归并排序求逆序数的写法不再解释。

参考代码

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
using std::cin;
using std::cout;
using std::endl;
typedef long long INT;
inline INT readIn()
{
bool minus = false;
INT a = 0;
char ch = getchar();
while (!(ch == '-' || ch >= '0' && ch <= '9')) ch = getchar();
if (ch == '-')
{
minus = true;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
a *= 10;
a += ch;
a -= '0';
ch = getchar();
}
if (minus) a = -a;
return a;
}

const INT mod = 99999997;
const INT maxn = 100005;
INT n;
struct info { INT height; INT index; INT sortIndex; } infos[2][maxn];
bool comp1(const info& a, const info& b)
{
return a.height < b.height;
}
bool comp2(const info& a, const info& b)
{
return a.index < b.index;
}
INT sequence[2][maxn];
INT temp[maxn];
INT ans;

void mergesort(INT x, INT y)
{
if (y - x == 1) return;
INT mid = (x + y) / 2;
mergesort(x, mid);
mergesort(mid, y);
INT i = x;
INT j = mid;
INT k = x;
while (i < mid || j < y)
{
if (j >= y || i < mid && sequence[1][i] <= sequence[1][j])
{
temp[k++] = sequence[1][i++];
}
else
{
ans += mid - i;
temp[k++] = sequence[1][j++];
}
}
for (i = x; i < y; i++)
{
sequence[1][i] = temp[i];
}
}

void run()
{
n = readIn();
for (int i = 0; i < n; i++)
{
infos[0][i].height = readIn();
infos[0][i].index = i;
}
for (int i = 0; i < n; i++)
{
infos[1][i].height = readIn();
infos[1][i].index = i;
}
std::sort(infos[0], infos[0] + n, comp1);
for (int i = 0; i < n; i++)
{
infos[0][i].sortIndex = i;
}
std::sort(infos[0], infos[0] + n, comp2);

std::sort(infos[1], infos[1] + n, comp1);
for (int i = 0; i < n; i++)
{
infos[1][i].sortIndex = i;
}
std::sort(infos[1], infos[1] + n, comp2);

for (int i = 0; i < n; i++)
{
sequence[0][infos[0][i].sortIndex] = i;
}
for (int i = 0; i < n; i++)
{
sequence[1][i] = sequence[0][infos[1][i].sortIndex];
}

mergesort(0, n);
cout << ans % mod << endl;
}

int main()
{
run();
return 0;
}


其实,要获取那个序列还有更简单的方法。我们直接建立index[0]到index[1]的映射就可以了。这里我们设了index[i]=i,所以可以这样赋值:

sequence[index1[i]] = index2[i];


另附间接排序代码。这次重写了一次间接排序,又没有出错,但是一定要明确排序后得到的是什么。毫无疑问,间接排序还要快一些。

//Indirect sort
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
using std::cin;
using std::cout;
using std::endl;
typedef long long INT;
inline INT readIn()
{
bool minus = false;
INT a = 0;
char ch = getchar();
while (!(ch == '-' || ch >= '0' && ch <= '9')) ch = getchar();
if (ch == '-')
{
minus = true;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
a *= 10;
a += ch;
a -= '0';
ch = getchar();
}
if (minus) a = -a;
return a;
}

const INT mod = 99999997;
const INT maxn = 100005;
INT n;
INT height1[maxn];
INT index1[maxn];
INT height2[maxn];
INT index2[maxn];
bool comp1(const INT& a, const INT& b)
{
return height1[a] < height1[b];
}
bool comp2(const INT& a, const INT& b)
{
return height2[a] < height2[b];
}
INT sequence[maxn];
INT temp[maxn];
INT ans;

void mergesort(INT x, INT y)
{
if (y - x == 1) return;
INT mid = (x + y) / 2;
mergesort(x, mid);
mergesort(mid, y);
INT i = x;
INT j = mid;
INT k = x;
while (i < mid || j < y)
{
if (j >= y || i < mid && sequence[i] <= sequence[j])
{
temp[k++] = sequence[i++];
}
else
{
ans += mid - i;
temp[k++] = sequence[j++];
}
}
for (i = x; i < y; i++)
{
sequence[i] = temp[i];
}
}

void run()
{
n = readIn();
for (int i = 0; i < n; i++)
{
height1[i] = readIn();
index1[i] = i;
}
for (int i = 0; i < n; i++)
{
height2[i] = readIn();
index2[i] = i;
}
std::sort(index1, index1 + n, comp1);
std::sort(index2, index2 + n, comp2);

for (int i = 0; i < n; i++)
{
sequence[index1[i]] = index2[i];
}

mergesort(0, n);
cout << ans % mod << endl;
}

int main()
{
run();
return 0;
}


除了归并排序可以求逆序数,还可以使用树状数组求逆序数。这还是第一次见,所以给出解析。

树状数组求逆序数

逆序数定义为前面的数大于后面的数的个数。求逆序数时,往往不是拿到一个数后往后找比自己小的数,而是往前找比自己大的数,因为前面的数是之前读入的,经过一些处理后就能快速求逆序数了。

树状数组便能成为一种处理方式。我们可以定义Sumi为小于等于i的数的个数(树状数组就是用来求1到i的和的)。当读入数i时,我们可以可以更新Sumi,同时计算出大于自己的数的个数,为已经读入的数−Sumi。

如果数字比较小并且没有负数,那么这是一种相当高效的算法。但是往往数据都是≤231−1的,甚至会有负数。这时又怎么解决呢?

可以先对数据排序,得出序号来,然后将序号看做原数。这样的话树状数组的大小就只与数据规模有关了。如果有相同的数,则需要将他们编为相同的号,但这就有些麻烦了,不如直接用归并排序来求。所以建议:当没有重复数据或数据较小且为正数时使用树状数组来求逆序数,有重复数据时使用归并排序来求逆序数。在适当条件下,树状数组要快一点。

参考代码

//Indirect sort + BIT
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
using std::cin;
using std::cout;
using std::endl;
typedef long long INT;
inline INT readIn()
{
bool minus = false;
INT a = 0;
char ch = getchar();
while (!(ch == '-' || ch >= '0' && ch <= '9')) ch = getchar();
if (ch == '-')
{
minus = true;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
a *= 10;
a += ch;
a -= '0';
ch = getchar();
}
if (minus) a = -a;
return a;
}

const INT mod = 99999997;
const INT maxn = 100005;
INT n;
INT height1[maxn];
INT index1[maxn];
INT height2[maxn];
INT index2[maxn];
bool comp1(const INT& a, const INT& b)
{
return height1[a] < height1[b];
}
bool comp2(const INT& a, const INT& b)
{
return height2[a] < height2[b];
}
INT sequence[maxn];
INT temp[maxn];

inline INT lowbit(INT x)
{
return x & -x;
}
INT getInv()
{
INT ans = 0;
std::vector<INT> BIT(n + 1);
for(int i = 0; i < n; i++)
{
INT x;
x = sequence[i] + 1; //必须从1开始,所以重编码
while(x <= n)
{
BIT[x]++;
x += lowbit(x);
}

INT count_ = 0;
x = sequence[i] + 1;
while(x > 0)
{
count_ += BIT[x];
x -= lowbit(x);
}
ans += i + 1 - count_;
}
return ans;
}

void run()
{
n = readIn();
for (int i = 0; i < n; i++)
{
height1[i] = readIn();
index1[i] = i;
}
for (int i = 0; i < n; i++)
{
height2[i] = readIn();
index2[i] = i;
}
std::sort(index1, index1 + n, comp1);
std::sort(index2, index2 + n, comp2);

for (int i = 0; i < n; i++)
{
sequence[index1[i]] = index2[i];
}
cout << getInv() % mod << endl;
}

int main()
{
run();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: