[SMOJ1787]逆序对

来源:互联网 发布:淘宝流量钱包不能用了 编辑:程序博客网 时间:2024/06/12 01:40

题目描述

Smart 得到了一个 1~n 的全排列。

Smart 每次会交换第 i 个数和第 j 个数,对于每一次交换,Smart 需要 Sarah 回答该全排列的逆序对数为多少。

“1、2、3、4、………、248289469!”Sarah 如是回答到。

Smart 觉得答案数太大,不太好判断是否正确,所以只需回答最后答案取模 2 的结果。

输入格式 1787.in

第一行一个整数 n

第二行为 1~n 的某个全排列;

第三行一个整数 m,表示交换操作的次数。

接下来 m 行,每行两个数 ij

输出格式 1787.out

输出 m 行,每行包含一个整数表示交换后答案。

输入样例 1787.in

1
1 2 3 4
1
1 2

输出样例 1787.out

1

数据范围

30%的数据:n,m1000
100%的数据:n,m100000


本题看似复杂,但破解了其中的奥妙之后,编码量十分短。

做法一:暴力做
直接按题意模拟,每次交换两个元素重新求逆序对
时间复杂度:取决于求逆序对的算法,两重 for 循环为 O(m×n2),树状数组为 O(m×nlog2n)
得分:30
代码:

#include <algorithm>#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>using namespace std;const int maxn = 1e5 + 100;#define lowbit(x) (x & -x)int n, m;int a[maxn];int tree[maxn];void update(int x) {    while (x <= n) {        tree[x]++;        x += lowbit(x);    }}int query(int x) {    int sum = 0;    while (x) {        sum += tree[x];        x -= lowbit(x);    }    return sum;}int calc() {    int ans = 0;    memset(tree, 0, sizeof tree);    for (int i = 1; i <= n; i++) {        update(a[i]);        ans += i - query(a[i]);    }    return ans;}int main(void) {    freopen("1787.in", "r", stdin);    freopen("1787.out", "w", stdout);    scanf("%d", &n);    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);    scanf("%d", &m);    for (int i = 0; i < m; i++) {        int x, y;        scanf("%d%d", &x, &y);        swap(a[x], a[y]);        printf("%d\n", calc() & 1);    }    return 0;}

做法二:
算出最初的逆序对数,之后每次交换就将答案取反(0 变 1,1 变 0)
时间复杂度:O(n+m+nlogn)
得分:100

理由:若每次交换排列中两个不相等的数,必会增加或减少奇数对逆序对。
如何证明?(感谢 oql 的指导)
不妨设被交换的数分别为 aiaj,且 i<j
于是有两种情况:ai>ajai<aj
为了方便,我们不妨将 n 个数按下标分为三部分,[1,i) 为 A 部分,(i,j) 为 B 部分,(j,n] 为 C 部分,如图。(注意 ij 不属于任何一部分,因为它们是待交换的)

首先,A、B、C 每个部分数中(不含 ij!)各自的逆序对数不受交换影响,这是显然的。

在 A 部分中的数,交换后对逆序对数没有影响。因为虽然 aiaj 的绝对位置交换了,但是它们相对于 A 部分中的数的相对位置依然是不变的。
同理,在 C 部分中的数,交换后对逆序对数也没有影响。

于是我们就要考虑 B 部分中的数,分三类讨论:

  • 大于 ai 的数,必然也大于 aj,不妨设为 x,易知交换前有逆序对 (x,aj),交换后有逆序对 (x,ai),故它们对逆序对数无影响。
  • 小于 aj 的数,必然也小于 ai,不妨设为 y,易知交换前有逆序对 (ai,y),交换后有逆序对 (aj,y),故它们对逆序对数无影响。
  • 小于 ai 而大于 aj 的数。不妨设为 z,易知交换前有逆序对 (ai,z)(z,aj),交换后无逆序对。若这样的数有 p 个,则逆序对数减少 2p 对。

最后,因为 ai>aj,所以交换前有逆序对 (ai,aj),交换后无逆序对。故,总共减少了 2p+1 对逆序对,显然减少的是奇数。

情况二:ai<aj 之类的,所以要判断一下,如果交换的是同一个数,答案与之前一致。

参考代码:

#include <algorithm>#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>using namespace std;const int maxn = 1e5 + 100;#define lowbit(x) (x & -x)int n, m;int a[maxn];int tree[maxn];void update(int x) {    while (x <= n) {        tree[x]++;        x += lowbit(x);    }}int query(int x) {    int sum = 0;    while (x) {        sum += tree[x];        x -= lowbit(x);    }    return sum;}int calc() {    int ans = 0;    memset(tree, 0, sizeof tree);    for (int i = 1; i <= n; i++) {        update(a[i]);        ans += i - query(a[i]);    }    return ans;}int main(void) {    freopen("1787.in", "r", stdin);    freopen("1787.out", "w", stdout);    scanf("%d", &n);    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);    scanf("%d", &m);    int x, y;    scanf("%d%d", &x, &y);    swap(a[x], a[y]);    int ans = calc() & 1; printf("%d\n", ans);    for (int i = 1; i < m; i++) {        scanf("%d%d", &x, &y);        if (x != y) ans = !ans;        printf("%d\n", ans);    }    return 0;}
1 0
原创粉丝点击