51Nod 1020 离线+DP

题目链接


题意:
定义一个排列的逆序数为排列中逆序对的个数
给出 n n n k k k,问 n n n位数的所有排列中,逆序数为 k k k的排列个数有多少。


思路:
考虑动态规划
定义 d p [ i ] [ j ] dp[i][j] dp[i][j]: i i i位数的排列中逆序数为 j j j的排列个数

考虑对于 n + 1 n+1 n+1位数的排列,可以看作是 ( n + 1 ) (n+1) (n+1)这个数插入到 n n n位数的排列中而形成。

那么对于 d p [ n + 1 ] [ k ] dp[n+1][k] dp[n+1][k]的求取:
可以考虑利用 ( n + 1 ) (n+1) (n+1)一定大于 1 − n 1-n 1n中的任意一个数的性质。

初始 d p [ n + 1 ] [ k ] = 0 dp[n+1][k] = 0 dp[n+1][k]=0
假设第 ( n + 1 ) (n+1) (n+1)这个数插入到 n n n位数的排列最后一位,且最后新的排列的逆序数为 k k k
则:
d p [ n + 1 ] [ k ] = d p [ n + 1 ] [ k ] + d p [ n ] [ k ] dp[n+1][k] = dp[n+1][k] + dp[n][k] dp[n+1][k]=dp[n+1][k]+dp[n][k]

若第 ( n + 1 ) (n+1) (n+1)这个数插入到 n n n位数的排列倒数第二位,因为 ( n + 1 ) (n+1) (n+1)一定是最大的数,故他跟他后面的数均会对答案有新的贡献,且贡献等于他后面的数的个数:
则:
d p [ n + 1 ] [ k ] = d p [ n + 1 ] [ k ] + d p [ n ] [ k − 1 ] dp[n+1][k] =dp[n+1][k] + dp[n][k-1] dp[n+1][k]=dp[n+1][k]+dp[n][k1]

…依此类推

d p [ n + 1 ] [ k ] = ∑ i = m a x ( 0 , k − n ) k d p [ n ] [ i ] dp[n+1][k] = \sum_{i=max(0,k-n)}^k dp[n][i] dp[n+1][k]=i=max(0,kn)kdp[n][i]

其中求和式可以利用前缀和进行优化时间复杂度。
因多组数据,可以考虑离线处理,优化空间复杂度。
最后总复杂度为: O ( n k + T ) O(nk + T) O(nk+T) n , k n,k n,k为多组数据中的最大值。

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
const int mod = 1e9 + 7;
const int A = 2e4 + 10;
int dp[A],sum[A],Ans[A],Mx_n,Mx_k,q;

class Qu{
public:
    int n,k,id;
    bool operator<(const Qu& rhs) const{
        if(n != rhs.n) return n < rhs.n;
        return k < rhs.k;
    }
}Q[A];

void solve(int n,int k){
    dp[0] = 1;
    int tot = 1;
    for(int i=2 ;i<=n ;i++){
        sum[0] = dp[0];
        while(tot<=q && Q[tot].n == i && Q[tot].k == 0){
                Ans[Q[tot].id] = 1;
                tot++;
        }
        for(int j=1 ;j<=k ;j++) sum[j] = (sum[j-1] + dp[j])%mod;
        for(int j=1 ;j<=k ;j++){
            if(j-i+1 > 0) dp[j] = (sum[j] - sum[j-i])%mod;
            else          dp[j] = sum[j];
            if(dp[j]<0) dp[j] += mod;
            while(tot<=q && Q[tot].n == i && Q[tot].k == j){
                Ans[Q[tot].id] = dp[j];
                tot++;
            }
        }
    }
    //printf("tot = %d\n",tot);
    for(int i=1 ;i<=q ;i++){
        printf("%d\n",Ans[i]);
    }
}

int main(){
    scanf("%d",&q);
    Mx_n = Mx_k = 0;
    for(int i=1 ;i<=q ;i++){
        scanf("%d%d",&Q[i].n,&Q[i].k);
        Q[i].id = i;
        Mx_n = max(Mx_n,Q[i].n);
        Mx_k = max(Mx_k,Q[i].k);
    }
    sort(Q+1,Q+1+q);
    solve(Mx_n,Mx_k);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值