15天大厂真题带刷 - ZT25 小红的子序列逆序对

小红的子序列逆序对

https://www.nowcoder.com/practice/189a109747604763932024984f856d99

题意

给出一个数组,求该数组所有的子序列中的逆序对数量之和是多少

数组长度1e5

思路

这种求所有子序列/子数组xxx值的和一般都是考虑单个元素的贡献,这里考虑的是单个逆序对的贡献,对于每个逆序对来说,剩下的n-2个元素每个元素都有选或不选两种选择,可以构成的子序列个数为2^(n-2),那么如果有x个逆序对的话,最后答案就是x*2^(n-2)

求逆序对可以用归并排序或是树状数组,求次幂需要用到快速幂

归并排序:大概的思想是分治,一个区间的逆序对数量=左边逆序对的数量+右边逆序对的数量+跨左右边界的逆序对的数量,前两个都可以在分治的子过程里计算,跨左右边界的话,其实就是考虑在合并2个已经排序的子数组的时候,如果发现逆序对,也就是a[i] > a[j] ,那么逆序对的数量就会增加mid-i+1,其中mid是合并的子数组的中间下标。因为数组是从小到大排序的,如果a[i]>a[j],那么左边数组在下标i后面的元素也一定>a[j]。

树状数组:

这里套了个离散化的板子

代码1

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;

/*
归并排序:分治
1.确定分界点 mid
2.递归排序左边和右边:左边跟右边都是有序的了
3.将两个有序的数组合并为一个有序的数组

*/

ll a[100010], n, b[100010];
ll ans = 0;

ll merge_sort(ll l, ll r) {
    if (l >= r) return 0;
    ll mid = (l + r) / 2;
    ll res = 0;
    res += merge_sort(l, mid);
    res += merge_sort(mid + 1, r);
    ll i = l, j = mid + 1, k = 1;
    while (i <= mid && j <= r) {
        if (a[i] <= a[j]) b[k++] = a[i], i++;
        else {
            res += mid - i + 1;
            b[k++] = a[j], j++;
        }

    }
    while (i <= mid) b[k++] = a[i], i++;
    while (j <= r) b[k++] = a[j], j++;
    for (int i = l, u = 1; i <= r; i++, u++) a[i] = b[u];
    //cout<<l<<" "<<r<<" "<<res<<endl;
    return res;
}
ll ksm(ll a, ll b, ll p) {
    ll res = 1;
    a %= p;
    while (b) {
//&运算当相应位上的数都是1时,该位取1,否则该为0。
        if (b & 1)
            res = 1ll * res * a % p; //转换为ll型
        a = 1ll * a * a % p;
        b >>= 1; //十进制下每除10整数位就退一位
    }
    return res;
}
//4 5 6 | 1 2 3
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    ans = merge_sort(1, n);
    cout << ans*ksm(2,n-2,mod)%mod << endl;


    return 0;
}

代码2

这个a[i]只有1e5, 不用离散化也可以

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e5 + 7;

ll ksm(ll a, ll b, ll p) {
    ll res = 1;
    a %= p;
    while (b) {
//&运算当相应位上的数都是1时,该位取1,否则该为0。
        if (b & 1)
            res = 1ll * res * a % p; //转换为ll型
        a = 1ll * a * a % p;
        b >>= 1; //十进制下每除10整数位就退一位
    }
    return res;
}

ll tr[N], n, a[N];
vector<ll>nums;

ll lowbit(int x) {
    return x & -x;
}

void update(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
ll query(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}


int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i], nums.push_back(a[i]);
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    int m = nums.size();
    ll res = 0;
    for (int i = 1; i <= n; i ++ ) {
        int tmp = lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin() + 1;
        res += query(m) - query(tmp);
        update(tmp, 1);
    }
    cout << res*ksm(2,n-2,mod) % mod << endl;

    return 0;
}

#牛客创作赏金赛#
15天大厂真题带刷Go题解 文章被收录于专栏

15天大厂真题带刷Golang题解

全部评论

相关推荐

程序员花海_:项目描述写的太少了 多写一点 先写业务 再写技术
点赞 评论 收藏
分享
01-28 16:12
中南大学 Java
几年前还没有chatgpt的时候,刷题真的是很痛苦。刷不出来只能看题解,题解有几个问题:第一个是每次看的写题解的人都不一样,很难有一个统一的思路;第二个也是最重要的是,题解只提供了作者自己的思路,但是没有办法告诉你你的思路哪里错了。其实很少有错误的思路,我只是需要被引导到正确的思路上面去。所以传统题解学习起来非常困难,每次做不出来难受,找题解更难受。但是现在chatgpt能做很多!它可以这样帮助你&nbsp;-1.&nbsp;可以直接按照你喜欢的语言生成各种解法的题解和分析复杂度。2.&nbsp;把题和你写的代码都发给它,它可以告诉你&nbsp;你的思路到底哪里有问题。有时候我发现我和题解非常接近,只是有一点点🤏想错了。只要改这一点点就是最优解。信心倍增。3.&nbsp;如果遇到不懂的题解可以一行一行询问为什么要这样写,chatgpt不会嫌你烦。有时候我觉得自己的range写错了,其实那样写也没错,只是chat老师的题解有一点优化,这个它都会讲清楚。4.&nbsp;它可以帮你找可以用同类型解法来做的题。然后它可以保持解法思路不变,用一个思路爽刷一个类型的题。如果题目之间思路又有变化,它会告诉你只有哪里变了,其他的地方还是老思路。5.&nbsp;它也可以直接帮你总结模板,易错点。经过chat老师的指导,我最大的改变是敢刷题了。之前刷题需要先找某一个人写的算法题repo,然后跟着某一个人他的思路刷他给的几个题。如果想写别的题,套用思路失败了,没有他的题解,也不知道到底哪里错了;看别人的题解,思路又乱了。这个问题在二分查找和dp类型的题里面特别常见。但是现在有chat老师,他会针对我的代码告诉我我哪里想错了,应该怎么做;还按照我写代码的习惯帮我总结了一套属于我的刷题模板。每天写题全是正反馈!
明天不下雨了:那我建议可以用 chatgpt atlas 或者 dia 去刷,也可以用 chrome 加个 ai 插件去刷 左边刷题右边 chat 效果很好
AI时代的工作 VS 传...
点赞 评论 收藏
分享
评论
2
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务