codeforces 961 E Tufurama

题目

One day Polycarp decided to rewatch his absolute favourite episode of well-known TV series “Tufurama”. He was pretty surprised when he got results only for season 7 episode 3 with his search query of “Watch Tufurama season 3 episode 7 online full hd free”. This got Polycarp confused — what if he decides to rewatch the entire series someday and won’t be able to find the right episodes to watch? Polycarp now wants to count the number of times he will be forced to search for an episode using some different method.

TV series have n seasons (numbered 1 through n ), the i-th season has ai episodes (numbered 1 through ai). Polycarp thinks that if for some pair of integers x and y ( x  < y) exist both season x episode y and season y episode x then one of these search queries will include the wrong results. Help Polycarp to calculate the number of such pairs!

Input
The first line contains one integer n (1  ≤ n  ≤  2·105) — the number of seasons.

The second line contains n integers separated by space a1 ,  a2 , …,  an ( 1  ≤ ai ≤  109 ) — number of episodes in each season.

Output
Print one integer — the number of pairs x and y ( x  < y) such that there exist both season x episode y and season y episode x.

Examples

Input
5
1 2 3 4 5
Output
0

Input
3
8 12 7
Output
3

Input
3
3 2 1
Output
2

Note
Possible pairs in the second example:

  1. x  = 1, y = 2 (season 1 episode 2 pair season 2 episode 1);
  2. x  = 2, y = 3 (season 2 episode 3 pair season 3 episode 2);
  3. x  = 1, y = 3 (season 1 episode 3 pair season 3 episode 1).

In the third example:

  1. x  = 1, y = 2 (season 1 episode 2 pair season 2 episode 1);
  2. x  = 1, y = 3 (season 1 episode 3 pair season 3 episode 1).

分析

官方解答
【题意】
已知所有的 (i,a[i]) ,求 {(i1,i2)|i1a[i2],i2a[i1]} 的这样 (i1,i2) 的配对一共有多少对

【分析】
假设现在要处理数对 (1,a[1]) ,那么能和它配对的数对是 {(i,a[i])|ia[1],1a[i]} ——由于 a[i] 都大于等于1,所以配对就是 {(i,a[i])|ia[1]} ,因此配对的数量就是 a[1]
处理 (2,a[2]) 以及之后的数对能不能也这么方便呢?现在我们来看 (2,a[2]) 的配对情况,可以和它配对的是 {(i,a[i])|ia[2],2a[i]} ,与 i=1 的情况不同的是,现在需要 a[i] 大于等于2,这不是对所有 a[i] 都满足的,但是,等一下——让等式不成立的,不就是 {(i,a[i])|a[i]=1} 吗?但是这些数对由于 i=1 ,只能与 (1,a[1]) 配对,后面的数不需要考虑它们了。如果在处理完 (1,a[1]) 以后,把这些数都删掉,那么处理 (2,a[2]) 的时候, 2a[i] 就对所有数对都满足了,那么和 (2,a[2]) 的配对就是删除后剩下的所有 {(i,a[i])|ia[2]}

上图片演示一下吧,红色箭头表示根据a[i]可以和那些配对
动态演示

【思路】
所以把思路简单的说就是,以i取1~n的顺序,计数与(i, a[i])配对的个数,并在一次计数完以后,把其中所有的第二项为i的数对去除。删除操作当然是用树状数组高效完成啦。
最后的最后,该算法计数了自己和自己配对,并对所有配对都计数了两次,最后要减去自己配对的数量再除以2。
【注意】
数对的a[i]超过n与等于n是一样的,已经比所有的i都大了,只需要截断到n就行了,便于放到树状数组中存放。

代码

树状数组是我自己瞎写的结构体。。看着有点混乱,多做了一件事就是先把1~n的lowbit算了一遍,因为感觉可以节约时间233

#include<stdio.h>
#include<vector>
using std::vector;

#define N_max 200005
vector<int> v[N_max];

int  n;
int a[N_max];
#define min(a,b) ((a)<(b)?(a):(b))
struct  BIT {
    int c[N_max] = { 0 };//每个ci维护的都是1~i的最新和
    int lowbit[N_max] = { 0 };
    int n;
    BIT(int _n = N_max) {
        n = _n;
        for (int i = 1; i <= N_max; ++i)
        lowbit[i] = (i&(-i));
    }
    void newBIT(int _n) {
        n = _n;
        for (int i = 1; i <= n; ++i)
            c[i] = 0;
    }
    //更新时向右(上)更新
    void update(int x, int num) {
        while (x <= n) {
            c[x] += num;
            x += lowbit[x];
        }
    }
    //求和时向左(下)依次求和
    int getsum(int x) {
        int s = 0;
        while (x > 0) {
            s += c[x];
            x -= lowbit[x];
        }
        return s;
    }
}bit;

int main() {
    scanf("%d", &n);
    bit.newBIT(n);

    for (int i = 1; i <= n; ++i)
        scanf("%d", a+i);
    for (int i = 1; i <= n; ++i) {
        v[a[i] = min(n, a[i])].push_back(i);
        bit.update(i , 1);
    }
    unsigned long long res = 0;
    for (int i = 1; i <= n; ++i) {
        res = res + bit.getsum(a[i]);
        for (auto x : v[i])
            bit.update(x, -1);
    }
    for (int i = 1; i <= n; ++i)
        if (i <= a[i])
            --res;

    printf("%llu\n", res / 2);
}

总结

按一定的顺序处理所有数据,使得处理过的数据变的没有意义,于是从“可以用来配对”的集合中删去,而删去操作和剩余可配对的个数交给树状数组维护,这种解题思想真的很巧妙,感觉很多地方都用的到。

最后,树状数组是最优美的数据结构,不接受反驳。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值