2021 五星级挑战 消除交叉

题目链接

相似题目:Luogu P1966 火柴排队

一些结论

首先看到交换相邻的,很容易能够想到逆序对。

接着再来看每个点都有确定的想要去的地方,那肯定是逆序对了呀!

有一个一眼可以看出的结论。

就是我们可以只调换终点相邻的,起点不调换。

或者只调换起点相邻的,终点不管。

证明很简单,相对性。

还有一个结论,为了消除交叉,最后一定是这种样子:

证明也很简单,反证法。

我们现在假如知道了一个航线的起点,那么我们就可以推出它的终点,比如样例:

4 号航线的起点在第一个,那么终点也应该在第一个。

2 号航线的起点在第二个,那么终点也应该在第二个。

构造目标数组

我们用 l[i] 代表终点为 i 的航线终点应该在哪,l 数组很好搞。

输入起点的时候统计一个辅助数组 cc[i] 的意思是起点为第 i 个的航线。

c 数组在输入起点的时候可以完成。

然后输入终点时,假如当前输入的是 x,只需访问一下 x 航线的起点。

l[i]=c[x],这样 l 数组就构造出来了,答案就是 l 数组的逆序对数量,很好证明。

how to do it?

如果我们让 l[i]=i 就是终点为 i 的航线应该在 i,就完事儿了。

于是乎,现在要干的事儿就是交换 l 数组里相邻的元素,使得 l 升序排列。

所需要的最少步骤数就是该排列的逆序对数。

证明:在一个排列里交换两个相邻的元素,该排列的逆序对数量减一或加一。

逆序对有两个经典解法:树状数组,归并排序。

树状数组代码较为简洁,因为这题不需要离散化,所以我就贴一个树状数组的代码吧:

复制代码
#include <iostream>
using namespace std;
long long n, x, ans;
int a[100005], b[100005], l[100005];
long long c[100005];
void add (int x){for (; x <= n; x += x & -x) ++ c[x];}
int query (int x){return x == 0 ? 0 : c[x] + query (x - (x & -x) );}
int main()
{
    ios::sync_with_stdio (false);
    cin >> n;
    for (int i = 1; i <= n; i ++)
    {
        cin >> x;
        a[x] = i;
    }
    for (int i = 1; i <= n; i ++)
    {
        cin >> x;
        l[i] = a[x];
    }
    for (int i = n; i >= 1; i --)
    {
        add (l[i]);
        ans += query (l[i] - 1);
    }
    cout << ans << endl;
    return 0;
}
复制代码

 

posted @   Xy_top  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示