牛客挑战赛32 D.放物品

题意:

n个物品编号为1~n,每个物品i有一个不能放的位置pi,保证p是一个排列。对于合法排列的每一对逆序<j,i>(j>i),它的贡献是 ( p o s i − p o s j ) ∗ ( j − i ) (pos_i-pos_j)*(j-i) (posiposj)(ji),问所有合法排列中的所有逆序的贡献的总和。(n<=1000)

题解:

首先我们注意到每一对物品的贡献可以单独求解,所以我们求每一对(i,j)的贡献,把它们加起来。
对于一对i和j,如果 p o s i 和 p o s j posi和pos_j posiposj确定了, ( p o s i − p o s j ) ∗ ( j − i ) (pos_i-pos_j)*(j-i) (posiposj)(ji)就确定了,这个对应位置可以贡献的答案是 ( p o s i − p o s j ) ∗ ( j − i ) ∗ 剩 余 数 字 的 合 法 排 列 数 (pos_i-pos_j)*(j-i)*剩余数字的合法排列数 (posiposj)(ji)
剩余数字有三种情况:

  1. i占了pj, j占了pi,剩余的每个数字都有一个不能放的位置,那么合法排列数就是全错位排列f[n]
  2. i和j其中一个占了另一个的位置,另一个占了别的数字的位置,剩余数字有一个数字可以随便放,令它的排列数为d[n]
  3. i和j都没占对方的位置,那么剩余数字有两个可以随便放,令它的排列数为g[n]

d[n]和g[n]可以dp求解。
对于d[n],如果那个自由数字占了那个自由的空,剩余排列就是f[n-1],如果占了其他数字的空,剩余排列是d[n-1],有(n-1)个其他数字,所以 d [ n ] = f [ n − 1 ] + ( n − 1 ) d [ n − 1 ] d[n]=f[n-1]+(n-1)d[n-1] d[n]=f[n1]+(n1)d[n1]
同理讨论两个自由元的位置可得 g [ n ] = 2 f [ n − 2 ] + 4 ( n − 2 ) d [ n − 2 ] + ( n − 2 ) ( n − 3 ) g [ n − 2 ] g[n]=2f[n-2]+4(n-2)d[n-2]+(n-2)(n-3)g[n-2] g[n]=2f[n2]+4(n2)d[n2]+(n2)(n3)g[n2]
然后分别求出每一对i和j三种情况下 ∣ p o s i − p o s j ∣ |pos_i-pos_j| posiposj的贡献,最后乘上(j-i)就是(i,j)的贡献了。

#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
using namespace std;
const int maxn = 1e3 + 50;
const ll mod = 998244353;
ll qm(ll a, ll b){
    ll res = 1;
    while(b){
        if(b&1) res = res*a%mod;
        a = a*a%mod;
        b >>= 1;
    }return res;
}
ll fac[maxn], ifac[maxn], f[maxn], d[maxn], g[maxn];
int p[maxn];
int main()
{
    fac[0] = ifac[0] = 1;
    for(int i = 1; i < maxn; ++i) {
        fac[i] = fac[i-1]*i%mod;
        ifac[i] = qm(fac[i], mod-2);
    }
    f[0] =1 ; f[1] = 0;
    for(int i = 2; i < maxn; ++i){
        f[i] = 0;
        for(int j = 2; j <= i; ++j){
            if(j&1){
                f[i] = (f[i] - fac[i]*ifac[j]%mod)%mod;
            }
            else f[i] = (f[i] + fac[i]*ifac[j]%mod)%mod;
        }
    }
    d[0] = d[1] = 1; d[2] = 1; d[3] = 3;
    g[0] = g[1] = 1; g[2] = 2; g[3] = 4;
    for(int i = 4; i < maxn; ++i){
        d[i] = (f[i-1] + (i-1)*d[i-1])%mod;
        g[i] = (2*f[i-2]%mod + 4*(i-2)%mod*d[i-2]%mod + (i-2)*(i-3)%mod*g[i-2]%mod)%mod;
    }
    int T;cin>>T;
    while(T--){
        ll n; cin>>n;
        for(int i = 1; i <= n; ++i){
            scanf("%d", &p[i]);
        }
        if(n <= 1){
            printf("0\n"); continue;
        }
        ll k = n*(n+1)%mod*(2*n+1)%mod*qm(12, mod-2)%mod - n*(n+1)%mod*qm(4, mod-2);
        k%=mod;
        ll ans = 0;
        for(int i = 1; i < n; ++i){
            for(int j = i+1; j <= n; ++j){
                ll tempk = k;
                if(p[j] < p[i]){//两个都命中
                    tempk -=(p[i] - p[j]);
                }
                //命中p[i]
                if(p[j] < p[i]){
                    tempk -= p[i]*(p[i]-1)/2 - (p[i] - p[j]);
                }
                else tempk -= p[i]*(p[i]-1)/2;
                //命中p[j]
                if(p[j] < p[i]){
                    tempk -= (n-p[j]+1)*(n-p[j])/2 - (p[i] - p[j]);
                }
                else tempk -= (n-p[j]+1)*(n-p[j])/2;
                tempk %= mod;
                tempk = (tempk+mod)%mod;
                //cout<<"tempk:"<<tempk<<endl;
                ll temp = tempk*g[n-2]%mod;//
                //cout<<"temp:"<<temp<<endl;
                //i和j互占位置
                if(p[j] > p[i]){
                    temp  = (temp - (p[j] - p[i])*g[n-2])%mod;
                    temp += (p[j] - p[i])*f[n-2]%mod;
                    temp %= mod;
                }
               // cout<<"t:"<<temp<<endl;
                //i在p[j],j不在p[i]
                if(p[j] < p[i]){//如果可占的位置中没有p[i]
                    temp -= g[n-2] * (p[j]*(p[j]-1)/2)%mod;
                    temp %= mod;
                    temp += d[n-2] * (p[j]*(p[j]-1)/2) %mod;
                    temp %= mod;
                }
                else {
                   // cout<<"p:"<<p[j]<<endl;
                    temp -= ( (p[j]*(p[j]-1)/2 ) - (p[j] - p[i]) )*g[n-2]%mod;
                    temp %= mod;
                    temp += ( (p[j]*(p[j]-1)/2) - (p[j] - p[i]) )*d[n-2]%mod;
                    temp %= mod;
                }
                //cout<<"temp:"<<temp<<endl;
                //j在p[i], i不在p[j]
                if(p[i] > p[j]){//i可占的位置中没p[j]
                    temp -= ( (n-p[i])*(n-p[i]+1)/2 )*g[n-2] %mod;
                    temp%= mod;
                    temp += ( (n-p[i])*(n-p[i]+1)/2 )*d[n-2] %mod;
                    temp %= mod;
                }
                else{
                    temp -= ((n-p[i])*(n-p[i]+1)/2 - (p[j] - p[i]))*g[n-2]%mod;
                    temp %= mod;
                    temp += ((n-p[i])*(n-p[i]+1)/2 - (p[j] - p[i]))*d[n-2]%mod;
                    temp %= mod;
                }
                //cout<<"i:"<<i<<" j:"<<j<<" temp:"<<temp<<endl;
                ans = (ans + temp*(j-i))%mod;
            }
        }
        ans %= mod;
        ans = (ans + mod)%mod;
        cout<<ans<<endl;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值