K-inversions URAL - 1523 (dp + 线段树优化)

 

Consider a permutation a 1, a 2, …, an (all ai are different integers in range from 1 to n). Let us call k-inversion a sequence of numbers i 1, i 2, …, ik such that 1 ≤  i 1 <  i 2 < … <  ik ≤  n and ai 1 >  ai2 > … >  aik. Your task is to evaluate the number of different k-inversions in a given permutation.

Input

The first line of the input contains two integers n and k (1 ≤  n ≤ 20000, 2 ≤  k ≤ 10). The second line is filled with n numbers ai.

Output

Output a single number — the number of k-inversions in a given permutation. The number must be taken modulo 10 9.

Example

inputoutput
3 2
3 1 2
2
5 3
5 4 3 2 1
10

Solution:

我们定义数组dp[i][j]代表以a[i]结尾的长度为j的序列的情况数量,那么可以得到状态转移方程:

dp[i][j] = \sum dp[k][j],其中k代表的是下标为1~i - 1中a[k] > a[i]的数的下标。有了状态转移方程问题就解决了,但是不能暴力跑,时间不允许,我们要用到大于a[i] 的dp值的和,可以用线段树来优化,logn的复杂度还是可以接受的。每次只要查询一下a[i]+1~n的区间和就好了(注意判断a[i] + 1 和 n 的大小关系),值得一提的是我开了10棵线段树,分别用来存放 j  从1~k的值(用树状数组做的话代码量更短,可惜我没学呢。。)。

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

typedef long long ll;
const int maxn = 2 * 1e4 + 100;
const int mod = 1e9;

ll dp[maxn][20];
int a[maxn], ma[maxn];
int n, k;
ll tree[maxn << 2][12];

void push_up(int rt, int pos) {
    tree[rt][pos] = (tree[rt << 1][pos] +tree[rt << 1 | 1][pos]) % mod;
}

void update(int pos, ll val, int floor, int l, int r, int rt)  //很常规的单点更新
{
    if(l == r && l == pos)  {
        tree[rt][floor] = val;
        return ;
    }
    int m = (l + r) >> 1;
    if(pos <= m)
        update(pos, val, floor, l, m, rt << 1);
    else
        update(pos, val, floor, m + 1, r, rt << 1 | 1);
    push_up(rt, floor);
}

ll query(int L, int R, int floor, int l, int r, int rt)    //很常规的区间查询
{
    if(L > R)        //顶多也就多了个判断是否合法
        return 0;
    if(l >= L && r <= R)  {
        return tree[rt][floor];
    }
    ll ans = 0;
    int m = (l + r) >> 1;
    if(m >= L)
        ans = (ans + query(L, R, floor, l, m, rt << 1)) % mod;
    if(m < R)
        ans = (ans + query(L, R, floor, m + 1, r, rt << 1 | 1)) % mod;
    return ans;
}

int main()
{
    //freopen("in.txt", "r", stdin);
    cin >> n >> k;
    for(int i = 1; i <= n; ++ i)
    {
        cin >> a[i];
    }
    memset(dp, 0, sizeof(dp));
    for(int i = 1; i <= n; ++ i)         //1~n放外层循环
    {
        for(int j = 1; j <= k; ++ j)     //1~k放内层循环
        {
            if(j == 1)
                dp[i][j] = 1;            //不能放在外面一次性初始化为1,我们每次需要的是在a[i]前面并且比a[i]大的数的dp值,而不是单单的比a[i]大的数的dp值
//我们更新dp[i][j]时需要的是j - 1时候的状态,如果一次性初始化为1的话,我们的查询结果就是大于a[i]的所有数的dp值了。
            else
                dp[i][j] = query(a[i] + 1, n, j - 1, 1, n, 1);
            update(a[i], dp[i][j], j, 1, n, 1);
        }
    }
    ll ans = 0;
    for(int i = 1; i <= n; ++ i)        //把以每个数为结尾的情况全部加起来就得到了最终的答案
        ans = (ans + dp[i][k]) % mod;
    cout << ans << endl;
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值