小白月赛61 E

题意

给你一个长度为n的序列,你需要对他进行全排列,求每种序列的逆序对有多少,然后求每种序列逆序对之和。

思路

如果这个长度为n的序列如果没有重复的数字,这个逆序对之和公式为 n ∗ ( n − 1 ) ∗ f a [ n ] 4 \frac{n*(n-1)*fa[n]}{4} 4n(n1)fa[n]
推理公式
但是这个序列还有重复的数字,例如1 2 3 3这个序列,我们应该按照1 2 3 4这样计算,但是这儿有两个3,所以我们应该把3 4这一组数字的所有贡献减去。而3 4在整个长度为n的序列中一共会出现 4 ! 2 ! \frac{4!}{2!} 2!4!这么多次,因为3 4也会有排列。然后3 4 每次会贡献 2 ∗ ( 2 − 1 ) ∗ f a [ 2 ] 4 \frac{2*(2-1)*fa[2]}{4} 42(21)fa[2],所以说我们应该在原来没有重复的基础上,减去这些重复元素的贡献也就是3 4的贡献。所以最终答案为: n ∗ ( n − 1 ) ∗ f a [ n ] 4 − 4 ! 2 ! ∗ 2 ∗ ( 2 − 1 ) ∗ f a [ 2 ] 4 \frac{n*(n-1)*fa[n]}{4} -\frac{4!}{2!}*\frac{2*(2-1)*fa[2]}{4} 4n(n1)fa[n]2!4!42(21)fa[2]

因此我们只需要判断那些数字重复出现过多少次,在原来的基础上每次相减就是了。

代码

#include<bits/stdc++.h>

#define IOS ios::sync_with_stdio(false);cin.tie(nullptr)
#define int long long 
#define endl "\n"
#define pb push_back 
#define pop_back pop 

using namespace std;

const int N = 1e5 + 10, INF = 1e18 + 10, mod = 1e9 + 7;

int arr[N], fa[N], ifa[N];
int n, m, k;

int ksm(int a, int b)
{
    int ans = 1;
    while (b)
    {
        if (b & 1) ans = a * ans % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

void init()
{
    fa[0] = 1;
    ifa[0] = 1;
    for (int i = 1; i < N; i++)
    {
        fa[i] = (i * fa[i - 1]) % mod;
        ifa[i] = ksm(i, mod - 2) * ifa[i - 1] % mod;
    }
}

signed main()
{
    IOS;
    init();
    int n;
    cin >> n;
    vector<int> a(n + 1);
    map<int, int>mp;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        mp[a[i]]++;
    }

    int ans = ((n * (n - 1)) % mod * fa[n] % mod * ksm(4, mod - 2) % mod) % mod;//不重复元素的贡献

    for (auto [k, v] : mp)
    {
        if (v > 1)//减去重复的元素的贡献,可以化简公式把fa[v]给约掉
        {
            ans = (ans - (fa[n] * v % mod * (v - 1) % mod * ksm(4, mod - 2)) % mod + mod) % mod;
        }
    }

    cout << ans << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值