POJ 3067 Japan


题目传送门

一、题目大意

两对岸,一边\(n\)个点,一边\(m\)个点,现在连\(k\)条线,问有几个交点。

二、题目解析

梳理一下这其实是一个问逆序对的问题,为什么是逆序对?

举例:

依题意可得上图,算出\(5\)个点,除了作图还有什么方法可以得到答案呢?我们不妨先把\(n\)的元素看成是有序的,例子就是如此(\(1\) \(2\) \(3\) \(3\))对应的是(\(4\) \(3\) \(2\) \(1\))因为有相同的数据存在,相同位的从小到大排序(\(4\) \(3\) \(1\) \(2\)),如果这个结果是(\(1\) \(2\) \(3\) \(4\))的话,是不是就没有交点了,因为没有逆序对存在。

把逆序对互换直到为零,操作步数就是\(5\),其实每添加一条线,增加的点数就是增加的逆序对数,还无法理解就按上述步骤模拟几组数据,明白要干什么。

剩下的就是非常经典的求逆序对问题了。

三、实现代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define lowbit(x) (x & -x)
typedef long long LL;
const int N = 1010;
LL t[N];

struct Node {
    int l, r;
    //按起点由大到小排序,如果起点一样的话,那么按终点由大到小排序
    //求逆序对的结构体
    bool operator<(const Node &W) const {
        if (l == W.l)
            return r > W.r;
        else
            return l > W.l;
    }
} a[N * N]; // k条边,上限是N*N条

void add(int x, int d) {
    while (x <= N) t[x] += d, x += lowbit(x);
}

LL getsum(int x) {
    LL sum = 0;
    while (x) sum += t[x], x -= lowbit(x);
    return sum;
}

int main() {
    int T, cas = 1;
    scanf("%d", &T);

    while (T--) {
        memset(t, 0, sizeof(t));
        int n, m, k;
        scanf("%d%d%d", &n, &m, &k); //岸的一边是n个,另一边是m个,下面的n,m没有再次用到
        // k条线
        for (int i = 1; i <= k; i++) scanf("%d%d", &a[i].l, &a[i].r);
        //由大到小排序
        sort(a + 1, a + 1 + k);

        LL ans = 0;
        //标准的树状数组求逆序对的过程
        for (int i = 1; i <= k; i++) {
            ans += getsum(a[i].r - 1);
            add(a[i].r, 1);
        }
        //输出
        printf("Test case %d: %lld\n", cas++, ans);
    }
    return 0;
}