NOIP 2013 Senior 2

来源:互联网 发布:淘宝评分低怎么办 编辑:程序博客网 时间:2024/05/29 11:03

涵涵有两盒火柴,每盒装有 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(aibi)

因此相当于要使

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

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

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

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

例如,两列火柴为

1 3 2 47 2 5 4

它们对应的sortIndex

1 3 2 44 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

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

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

参考代码

//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;}
原创粉丝点击