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的和)>乱序和>逆序和,因此只需要让两列火柴中每对火柴排序后对应的序号相等就可以了。
为了防止间接排序出现问题,这里使用直接排序:
首先,根据
例如,两列火柴为
它们对应的
则1该放的位置为1,2该放的位置为3,3该放的位置为2,4该放的位置为4。
用该放的位置去替换排序序号
最后求出这个序列的逆序数便是答案。
现在的问题就成了,如何求逆序数。逆序数可以使用老生常谈的归并排序来求,在考试时我也使用的是归并排序。归并排序求逆序数的写法不再解释。
参考代码
其实,要获取那个序列还有更简单的方法。我们直接建立index[0]到index[1]的映射就可以了。这里我们设了index[i]=i,所以可以这样赋值:
另附间接排序代码。这次重写了一次间接排序,又没有出错,但是一定要明确排序后得到的是什么。毫无疑问,间接排序还要快一些。
除了归并排序可以求逆序数,还可以使用树状数组求逆序数。这还是第一次见,所以给出解析。
树状数组便能成为一种处理方式。我们可以定义Sumi为小于等于i的数的个数(树状数组就是用来求1到i的和的)。当读入数i时,我们可以可以更新Sumi,同时计算出大于自己的数的个数,为已经读入的数−Sumi。
如果数字比较小并且没有负数,那么这是一种相当高效的算法。但是往往数据都是≤231−1的,甚至会有负数。这时又怎么解决呢?
可以先对数据排序,得出序号来,然后将序号看做原数。这样的话树状数组的大小就只与数据规模有关了。如果有相同的数,则需要将他们编为相同的号,但这就有些麻烦了,不如直接用归并排序来求。所以建议:当没有重复数据或数据较小且为正数时使用树状数组来求逆序数,有重复数据时使用归并排序来求逆序数。在适当条件下,树状数组要快一点。
参考代码
每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要 交换多少次?如果这个数字太大,请输出这个最小交换次数对 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; }
相关文章推荐
- NOIP 2013 火柴排队 (证明+乱搞之后的逆序对)
- 【NOIP2013提高组】火柴排队
- noip2013 火柴排队
- Noip提高2013 Day1 T2 火柴排队 归并求逆序对
- [noip2013tg] 火柴排队
- 【NOIP2013】火柴排队
- 【NOIP2013提高组day1】火柴排队
- NOIP2013火柴排队[逆序对]
- 【NOIP2013】火柴排队(逆序对)
- [NOIP2013]火柴排队
- 【NOIP2013】火柴排队
- 【NOIP2013】火柴排队 线段树+逆序对
- Noip2013 Day1 T2 火柴排队(归并排序/树状数组 求逆序对)
- NOIP2013 火柴排队
- noip2013火柴排队
- 【noip 2013】火柴排队
- 「2013NOIP提高组」火柴排队
- P1092 - 【NOIP2013】火柴排队 解题报告
- NOIP2013 火柴排队
- NOIP 2013 火柴排队