logo AlgoBeat OnlineJudge
登录 注册

(蓝,LIS 优化)P6885 [COCI 2016/2017 #3] Zoltan 题解

作者: joe_zxq 彩笔  ·  发布于 2026-07-13 15:45:50  ·  最后修改于 2026-07-13 15:45:57
已通过
审核员:joe_zxq 彩笔 · 2026-07-13 15:45:57

题意

个正整数按顺序依次加入到数列里,可以决定将这个数写在当前数列的最左边或最右边。写下的数列的可能的最长严格上升子序列的长度是多少,搞出这样的子序列有多少种。加入数列的方法或子序列下标不同则认为两子序列不同。

此题还出现在:「雅礼集训 2017 Day10」数列

思路

我们考虑取出 LIS 中那个最先被加入到数列中的位置(称其为 首位置),将 LIS 断开成左右两个部分。根据加入数列的方式,这个位置的原始下标是最小的。从此位置开始,前面的越往左,原始下标越大,数值越小;后面的越往右,原始下标越大,数值越大。

根据此发现,得出前面的部分以严格下降子序列的形式出现在原数组中;后面的部分以严格上升子序列的形式出现在原数组中。

对于原数组中的每一个位置 ,我们钦定其为最终 LIS 的 首位置,其原始下标是 LIS 中最小的。那么其余的 LIS 中的元素的原始下标都大于 ,于是结合之前的推论,我们只需要找到 中的最长严格上升和下降子序列然后合并即可。因为两个都是严格上升下降的,所以没有交集,可以独立地求。

后缀 LIS 考虑倒着一位位地算 LIS,树状数组优化 dp 的求法不必赘述了吧。在维护最大值的过程中,同时维护数量,即遇到更大的则覆盖,遇到相同的就把数量加上去。

最终对于每个位置 ,设最长上升子序列长度为 ,方案数为 ;最长下降子序列长度为 ,方案数为 。则总长度数是 ,方案数是

对于未被选入 LIS 的 个位置,只要插进去了就行,从前面插还是从后面插无所谓,有 种插法。总答案为

时间复杂度:

代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long

const ll N = 2e5 + 5, mod = 1e9 + 7;
ll n, a[N], mx, ct;

struct node {
    ll val = 0, cnt = 0;
};
node fu[N], fd[N];

ll tot;
set<ll> s;
unordered_map<ll, ll> mp;

void D() {
    for (ll i = 1; i <= n; i++) {
        cin >> a[i];
        s.insert(a[i]);
    }

    for (ll o : s) {
        mp[o] = ++tot;
    }

    for (ll i = 1; i <= n; i++) {
        a[i] = mp[a[i]];
    }
}

ll to(ll x) {
    return n + 1 - x;
}

ll qpow(ll x, ll y) {
    if (y == 0) {
        return 1;
    }

    ll res = qpow(x, y / 2);
    res = res * res % mod;

    if (y % 2) {
        res = res * x % mod;
    }

    return res;
}

struct bit {

    node tr[N];

    node merge(node x, node y) {
        if (x.val == y.val) {
            return { x.val, (x.cnt + y.cnt) % mod };
        } else if (x.val > y.val) {
            return x;
        } else {
            return y;
        }
    }

    void update(ll x, node k) {
        for (; x < N; x += x & -x) {
            tr[x] = merge(tr[x], k);
        }
    }

    node query(ll x) {
        node res = { 0, 1 };

        for (; x; x -= x & -x) {
            res = merge(res, tr[x]);
        }

        return res;
    }

} tu, td;

void solve() {
    cin >> n;
    D();

    for (ll i = n; i >= 1; i--) {
        fd[i] = td.query(a[i] - 1), fd[i].val++, td.update(a[i], fd[i]);
        fu[i] = tu.query(to(a[i] + 1)), fu[i].val++, tu.update(to(a[i]), fu[i]);
    }

    for (ll i = 1; i <= n; i++) {
        ll len = fd[i].val + fu[i].val - 1;

        if (len > mx) {
            mx = len, ct = fd[i].cnt * fu[i].cnt % mod;
        } else if (len == mx) {
            (ct += fd[i].cnt * fu[i].cnt % mod) %= mod;
        }
    }

    cout << mx << " " << ct *qpow(2, n - mx) % mod << "\n";
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    solve();
    return 0;
}

暂无评论

登录 后即可评论。