【高手训练】【树状数组】导线问题

题目

哈蒙有\(n\)条导线排成一排,每条导线有一个电阻值,神奇的电光只能从一根导线传到电阻比它大的上面,而且必须从左边向右传导,当然导线不必是连续的。

哈蒙想知道电光最多能通过多少条导线,还想知道这样的方案有多少。

Solution

简化题意:LIS及其方案数,\(a_i \leq n, n \leq 100010\)

最长上升子序列\(n^2\)显然很好做,DP入门题。我们再考虑一下如何实现\(n log_2n\)

我们可以用一个什么数据结构维护,比如树状数组。

对于一个需要转移的\(i\),我们要找的是在\(i\)之前的\(j\),且 满足\(a_j < a_i\)\(f_j\)的最大值。

树状数组从左至右加入本就保证\(j\)\(i\)之前,以\(a_i\)为下标建权值树状数组,维护\(f_i\)前缀最大值,这样找出来的\(f_j\)必定合法且最优,直接转移即可。

方案数也没什么问题。

观察一个基本的数据:

\(a_i\) LIS 方案数
2 1 1
1 1 1
4 2 2
3 2 2
5 3 4

我们可以理解为

最长上升子序列方案数必定是每层个数的累乘, 而本质就是对于某个长度累加每个方案数,得到下一值的方案数。

而我们只需要在转移的时候把前缀最大值改成累加,注意方案数随最优值更新。

\(\mathrm{Code:}\)

#include <bits/stdc++.h>

const int mod = 123456789;
const int N   = 200010;
int n, m;
int a[N] = {};

inline int add(int a, int b) { return a + b >= mod ? a + b - mod : a + b; }
inline void Add(int &a, int b) { a = add(a, b); }
template <class T>
void read(T &s) {
    T w    = 1;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-') c = getchar();
    if (c == '-') w = -1, c = getchar();
    while (c <= '9' && c >= '0')
        s = (s << 1) + (s << 3) + c - '0', c = getchar();
    s *= w;
}
template <class T>
void write(T x) {
    if (x < 0) x = ~x + 1, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
    return void();
}

int f[N] = {}, s[N] = {};
struct BIT {
    int c[N], c1[N];
    inline void inc(int x, int v, int s) {
        for (++x; x <= N - 10; x += x & (-x)) {
            if (v == c[x]) Add(c1[x], s);
            if (c[x] < v) c[x] = v, c1[x] = s;
        }
    }
    inline int ask(int x) {
        int maxn = -1;
        for (++x; x; x -= x & (-x)) maxn = std ::max(maxn, c[x]);
        return maxn;
    }
    inline int Ask(int x) {
        int sum = 0, now = 0;
        for (++x; x; x -= x & (-x)) {
            if (c[x] == now) Add(sum, c1[x]);
            if (c[x] > now) now = c[x], sum = c1[x];
        }
        return sum;
    }
} T;

main() {
    int maxx = 0;
    read(n);
    read(m);
    for (int i = 1; i <= n; ++i) read(a[i]);
    f[1] = 1;
    s[1] = 1;
    T.inc(a[1], f[1], s[1]);
    for (int i = 2; i <= n; ++i) {
        f[i] = T.ask(a[i] - 1) + 1;
        s[i] = T.Ask(a[i] - 1);
        if (f[i] == 1) s[i] = 1;
        T.inc(a[i], f[i], s[i]);
        maxx = std ::max(maxx, f[i]);
    }
    write(maxx);
    putchar(10);
    if (m == 0) return 0;
    int ans = 0;
    for (int i = 1; i <= n; ++i)
        if (f[i] == maxx) Add(ans, s[i]);
    write(ans);
    return 0;
}
posted @ 2020-06-12 14:59  云烟万象但过眼  阅读(119)  评论(0编辑  收藏  举报