NC54585 小魂和他的数列
Posted 空白菌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NC54585 小魂和他的数列相关的知识,希望对你有一定的参考价值。
题目
题目描述
一天,小魂正和一个数列玩得不亦乐乎。
小魂的数列一共有n个元素,第i个数为Ai。
他发现,这个数列的一些子序列中的元素是严格递增的。
他想知道,这个数列一共有多少个长度为K的子序列是严格递增的。
请你帮帮他,答案对998244353取模。
对于100%的数据,1≤ n ≤ 500,000,2≤ K ≤ 10,1≤ Ai ≤ 109。
输入描述
第一行包含两个整数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。
仿照最长上升子序列的状态,设 \\(f_i,j\\) 为以第 \\(i\\) 个数结尾且长度为 \\(j\\) 的上升子序列个数,显然是 \\(O(n^2k)\\) 的。其中可以优化的步骤是,查找上一个比自己小的元素。对于最长上升子序列优化查找的步骤,通常有三种方法,我们依次考虑是否适合用于这道题的状态:
- 改变状态,设 \\(f_i\\) 为长度为 \\(i\\) 的上升子序列的最小结尾数字,其有单调递增的性质,因此每次新增数字,二分查找最后一个小于自己数字的位置即可。但是,这个显然不适合用来优化这道题的状态。
- 优化查找,用数据结构维护前缀权值最大值,即以数字作为下标维护每个数字结尾的最大长度。这个方法是可以考虑的,我们将维护最大长度改为各个长度的上升子序列个数即可。
- 排序+优化查找,将数字顺序改为从小到大输入,用数据结构维护前缀最大值,即在原本的区间上维护每个位置结尾的最大长度,输入顺序保证了每次询问的答案一定都是比自己小的数字构成的答案,都是可以接上的。这个方法同样也是可以考虑的,我们将维护最大长度改为各个长度的上升子序列个数即可。
第二种方法需要先离散化,我们这里使用的是第三种方法,先排序后优化查找,复杂度上是没有区别的。
需要注意的是,使用第三种方法从小到大枚举时,因为要求的是上升子序列,所以相等的数字不能直接更新到数据结构中,需要等到所有相等的数字都查询完,才能一并更新。第二种方法由于直接维护权值关系,大小可以直接确定,则没有这种情况。
时间复杂度 \\(O(nk \\log n)\\)
空间复杂度 \\(O(nk)\\)
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int P = 998244353;
template<class T>
class Fenwick
int n;
vector<T> node;
public:
Fenwick(int _n = 0) init(_n);
void init(int _n)
n = _n;
node.assign(n + 1, T());
void update(int x, T val) for (int i = x;i <= n;i += i & -i) node[i] += val;
T query(int x)
T ans = T();
for (int i = x;i;i -= i & -i) ans += node[i];
return ans;
;
int k;
struct T
array<int, 17> f = ;
T &operator+=(const T &x)
for (int i = 1;i <= k;i++) (f[i] += x.f[i]) %= P;
return *this;
;
pair<int, int> a[500007];
int main()
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n >> k;
for (int i = 1;i <= n;i++)
int x;
cin >> x;
a[i] = x,i ;
sort(a + 1, a + n + 1, [&](auto a, auto b) return a.first < b.first;);
int ans = 0;
Fenwick<T> fw(n);
vector<pair<int, array<int, 17>>> v;
for (int i = 1;i <= n;i++)
if (a[i].first != a[i - 1].first)
for (auto [id, f] : v) fw.update(id, f );
v.clear();
auto res = fw.query(a[i].second).f;
for (int j = k;j >= 1;j--) res[j] = res[j - 1];
res[1] = 1;
(ans += res[k]) %= P;
v.push_back( a[i].second,res );
cout << ans << \'\\n\';
return 0;
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/17375838.html
小魂和他的数列-(离散+二分+树状数组)
https://ac.nowcoder.com/acm/problem/54585
题意:给500000个数构成一个数列,求递增个数为k的子序列个数,2<=k<=10。
题解:
1.求递增子序列个数,子序列不是子串,可以散乱分布。原数组为a,排序后为数组b,遍历a数组,每次求得ai在数组b的下标位置,设为pos,在树状数组里对pos位置进行累加,维护k个树状数组,递增个数为j的子序列 需要 递增个数为j-1的子序列的数据。c[i][j]表示的递增个数为i的子序列在j这个位置的个数。
2.一开始很难理解为什么要这样做,在纸上比划了许多下才懂,树状数组中还没有出现的值都是0,和逆序对有些相似。
3.大量数据输入用Java解题需要自行封装输入模板
import java.io.*; import java.util.StringTokenizer; import java.math.BigInteger; import java.util.Arrays; public class Main { static int [] a=new int[500010]; static int [] b=new int[500010]; static int [][] c=new int[11][500010]; static int n=0; static int p=998244353; public static void main(String[] args) { InputStream inputStream = System.in;//InputStream是表示字节输入流的所有类的超类 OutputStream outputStream = System.out; InputReader sc = new InputReader(inputStream); PrintWriter out = new PrintWriter(outputStream); Task solver = new Task(); solver.solve(sc, out);//这里当作原来的Main函数,输入输出都在里面解决 out.close();//关闭输出流 } static class Task { public void solve(InputReader scan, PrintWriter out) { n=scan.nextInt(); int k=scan.nextInt(); for(int i=1;i<=n;i++) { a[i]=scan.nextInt(); b[i]=a[i]; } Arrays.sort(b,1,n+1); for(int i=1;i<=n;i++) { int pos=check(1, n,a[i]); //System.out.println("pos="+pos); add(1, pos, 1); for(int j=2;j<=Math.min(k, i);j++) { add(j, pos, get_sum(j-1, pos-1)); } } System.out.println(get_sum(k, n)); } public static int lowbit(int x) { return (-x)&x; } public static void add(int i,int x,int val) { while(x<=n) { c[i][x]=(c[i][x]+val)%p; x=x+lowbit(x); } } public static int get_sum(int i,int x) { int res=0; while(x!=0) { res=(res+c[i][x])%p; x-=lowbit(x); } return res; } //二分 起始都是false,如果要找的x相同,先找一段相同的数的最左边,然后标记 public static int check(int l,int r,int x) { int mid=-1; while(l<=r) { mid=(l+r)/2; //System.out.println("x="+x+" l="+l+" r="+r+" mid="+mid); if(b[mid]>x) //往左 r=mid-1; else if(b[mid]<x) //往右 l=mid+1; else break; } return mid; } } //自己写出Scanner原本的输入语法,封装在InputReader类里 static class InputReader { public BufferedReader reader; public StringTokenizer tokenizer; public InputReader(InputStream stream) { reader = new BufferedReader(new InputStreamReader(stream), 32768); //32768是输入缓冲区大小,随便设的 tokenizer = null; } public String next() { while (tokenizer == null || !tokenizer.hasMoreTokens()) { try { tokenizer = new StringTokenizer(reader.readLine()); } catch (IOException e) { throw new RuntimeException(e); } } return tokenizer.nextToken(); } public int nextInt() { return Integer.parseInt(next()); } public long nextLong() { return Long.parseLong(next()); } public double nextDouble() { return Double.parseDouble(next()); } public boolean hasNext() { try { String string = reader.readLine(); if (string == null) { return false; } tokenizer = new StringTokenizer(string); return tokenizer.hasMoreTokens(); } catch (IOException e) { return false; } } public BigInteger nextBigInteger() {//大数 return new BigInteger(next()); } } }
以上是关于NC54585 小魂和他的数列的主要内容,如果未能解决你的问题,请参考以下文章