小魂和他的数列(dp+树状数组优化)

链接:https://ac.nowcoder.com/acm/contest/3566/C
来源:牛客网


Sometimes, even if you know how something’s going to end, that doesn’t mean you can’t enjoy the ride.
有时候,即使你知道了故事的结局,也不代表你不可以享受它的过程。

小魂和他的数列

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 131072K,其他语言262144K
64bit IO Format: %lld

题目描述

一天,小魂正和一个数列玩得不亦乐乎。
小魂的数列一共有n个元素,第i个数为Ai。
他发现,这个数列的一些子序列中的元素是严格递增的。
他想知道,这个数列一共有多少个长度为K的子序列是严格递增的。
请你帮帮他,答案对998244353取模。
对于100%的数据,1≤ n ≤ 500,000,2≤ K ≤ 10,1≤ Ai ≤ 10^9。
输入描述:
第一行包含两个整数n,K,表示数列元素的个数和子序列的长度。
第二行包含n个整数,表示小魂的数列。
输出描述:
一行一个整数,表示长度为K的严格递增子序列的个数对998244353取模的值。

示例1

输入
复制

5 3
2 3 3 5 1

输出

2

说明

两个子序列分别是2 3 3 5 1和2 3 3 5 1。

思路:

确定状态:
dp[i][j]:以i结尾长度为j的严格上升子序列的个数。
那么有状态转移方程:
d p [ i ] [ j ] = ∑ k = 1 i − 1 ( a [ k ] < a [ i ] ) ∗ d p [ k ] [ j − 1 ] dp[i][j] = \sum_{k=1}^{i-1} (a[k] < a[i]) * dp[k][j-1] dp[i][j]=k=1i1(a[k]<a[i])dp[k][j1]
根据状态转移方程可以写出:

for(int i = 1; i <= n; i++)
{
    for(int k = 1; k < i; k++)
    {
        for(int j = 1; j <= K; j++)
        {
            dp[i][j] += (a[k ] < a[i]) * dp[k][j-1];
        }
    }
}

时间复杂度: O(k*n^2)
很明显时间复杂度不达标。
此时想优化:
上面第二层循环(k < i)就是求前缀和,K最大为10,
所以可以利用树状数组来优化,开K棵树状数组,
每个树状组数组存长度为j的严格上升子序列的个数。
即:

for(int i = 1; i <= n; i++)
{
    for(int j = 1; j <= K; j++)
    {
        dp[i][j] += query(id,pos);
    }
}

时间复杂度: O(k*nlogn)
此时已经可以在规定的复杂度里求出结果。

对于每一个数插入树状数组前的处理:

第一种方式是直接排序(特别注意排序规则),不去重,具体见下面的code1
第二种是离散化(a[i]比较大,可能出现很多重复的值)+去重处理,具体见下面的code2

AC代码:

个人觉得code1更好理解

code1:

 /*
 dp[i][j]:以i结尾长度为j的严格上升子序列的个数。
 状态转移方程:dp[i][j] +=(a[j]从小到大排序),
 相同大小的(没有特判,程序会统一当作a[j]
#include 
using namespace std;
const int N = 5e5+5;
const int mod = 998244353;
struct Node
{
    int x;
    int pos;
    bool operator<(const Node& n)const
    {
        if(x == n.x)
        {
            return pos > n.pos;
        }
        return x < n.x;
    }
} a[N];
int n,k;
int sum[12][N];
inline int lowbit(int x)
{
    return x&-x;
}
void add(int id,int pos,int v)
{
    while(pos <= n)
    {
        sum[id][pos] = (sum[id][pos] + v) % mod;
        pos += lowbit(pos);
    }
}
int query(int id,int pos)
{
    int res = 0;
    while(pos)
    {
        res = (res + sum[id][pos]) % mod;
        pos -= lowbit(pos);
    }
    return res;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d",&a[i].x);
        a[i].pos = i;
    }
    sort(a+1,a+n+1);
    int ans = 0;
    //按输入的顺序插入树状数组
    for(int i = 1; i <= n; i++)
    {
        add(1,a[i].pos,1);
        for(int j = 2; j <= k; j++)
        {
            if(j > a[i].pos) break;//序列长度达不到j
            int v = query(j-1,a[i].pos-1);
            add(j,a[i].pos,v);
            if(j == k) ans = (ans + v) % mod;
        }
    }
    printf("%d\n",ans);
    return 0;
}

code2:


/*
如果去重(sort+unique+erase),
就要知道插入那个数它排第几
(low_bound(aim_arr_begin,aim_arr_end,x):
 返回im_arr中第一个大于等于x的地址)
*/

#include 
using namespace std;
const int N = 5e5+5;
const int mod = 998244353;
int n,k;
int a[N];
vector<int>vec;
int sum[12][N];
inline int lowbit(int x)
{
    return x&-x;
}
void add(int id,int pos,int v)
{
    while(pos <= n)
    {
        sum[id][pos] = (sum[id][pos] + v) % mod;
        pos += lowbit(pos);
    }
}
int query(int id,int pos)
{
    int res = 0;
    while(pos)
    {
        res = (res + sum[id][pos]) % mod;
        pos -= lowbit(pos);
    }
    return res;
}
int main()
{

    scanf("%d%d",&n,&k);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d",&a[i]);
        vec.push_back(a[i]);
    }
    //离散化处理
    sort(vec.begin(),vec.end());
    vec.erase(unique(vec.begin(),vec.end()),vec.end());
    //确定每个数的大小名次
    for(int i = 1; i <= n; i++)
    {
        //这里要注意:+1,要不然下面(a[i]-1)会数组越界,而且树状数组也是从1开始
        a[i] = lower_bound(vec.begin(),vec.end(),a[i])-vec.begin()+1;
    }
    int ans = 0;
    //这里按每个数的名次插入树状数组即可求前缀和
    for(int i = 1; i <= n; i++)
    {
        add(1,a[i],1);
        for(int j = 2; j <= k; j++)
        {
            int v = query(j-1,a[i]-1);
            add(j,a[i],v);
            if(j == k) ans = (ans + v) % mod;
        }
    }
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(动态规划,牛客)