P2487 [SDOI2011] 拦截导弹 题解

题目链接:拦截导弹

约定:

本题中提到的 \(LDS\)\(LIS\) 不是严格单调,而是非严格单调,即为 \(\le 或者 \ge\)

比较神奇的题,问的东西比较多,我们逐渐拆分:

对于第一个问题而言,这个 dp 方程是很好写的:

\[dp[i]=\max{dp[j]}+1 (i<j,h[i] \le h[j],v[i]\le v[j]) \]

其中 \(dp[i]\) 即为以 \(i\) 结尾的最多导弹数。那么答案显然即为 \(\max{dp}\),这玩意和最长递增子序列那些都是差不多的,注意到转移条件为三维偏序,那么我们使用 \(cdq分治\) 优化成 \(n\log^2{n}\) 就行了,比较经典。

注意第二个问题问的概率,对于某个导弹被记录的概率,首先需要知道包括这个导弹的总方案数,以及总方案数中有多少个方案是可以击落的。注意,此时此刻问的方案数都是基于第一问的最长的那个 \(LDS\) 的基础上而言,所以我们还要考虑当前点是否有 \(LDS\) 的构造方案。

听上去比较复杂是吧,我们注意到一个 \(LDS\) 可以被拆为两个 \(LDS\) 拼接。

如图所示,如果 \(i\) 是在 \(LDS\) 当中,那么一定可以分为两部分,以 \(i\) 结尾的 \(LDS\) 和以 \(i\) 开头的 \(LDS\),如果我们的红色方案数为 \(x\),蓝色的为 \(y\),则包含 \(i\)\(LDS\) 的方案数即为 \(x \times y\)。而总方案数,其实在第一个问题当中,也可以一并算出来了。

注意到难点,关于 \(i\) 开头的 \(LDS\) 并无法直接 \(dp\) 得到,因为我们的 \(dp\) 为了维持无后效性,那么思考下以 \(i\) 开头的 \(LDS\) 如果我们从右往左看,它就是以 \(i\) 结尾的 \(LIS\),那么从右往左 \(dp\) 就对了:

\[dp[i]=\max{dp[j]}+1 (i<j,h[i]\ge h[j],v[i] \ge v[j]) \]

严格地注意严格不等式和非严格不等式,只需要 \(bit\) 维护最值 \(dp\) 和对应方案数即可。做两遍 \(cdq\) 优化这两者 \(dp\)。像第一维这种和下标有关的序,我们直接按照下标序写就行了,这样就不需要再单独写这个序的排序函数。当然值域很大,顺便离散化一下。

这题码量重复性的东西比较多,码量不小,简单说说。

对于 \(cdq分治\) 优化 dp 具体经典思路,是基于中序遍历分治树,在访问完左子树时开始更新,注意一点,原偏移量为了保证第一层偏序,所以并不能直接排序,因为我们不是传统的 \(cdq\) 分治为后序遍历分治树,在中序遍历右子树时,如果还是按以往那种偏序,会破坏掉第一层偏序,导致访问右子树时计算不出答案。我们通常做法是记录下标,然后将下标按照序进行排序,这样第一层偏序就会在统计时依旧保证正确。这是一个经典的套路,用 \(cdq分治\) 优化带有偏序转移的 \(dp\) 问题。

关于用线段树还是树状数组好,一般树状数组就行了,其实这个问题抽象一下,询问一块值域内,最大的区间最大值,以及区间最大值的个数。这玩意线段树很好写 pushUp,分讨一下就行了,树状数组改为维护前后缀最值就行了。一般只涉及前后缀最值,都习惯用树状数组。

最终统计答案时,很显然只有前后缀最长拼起来:

\[pre[i]+suf[i]-1=maxLen,才需要统计它所包含的方案数 \]

\[ans=\frac{PreCnt \times SufCnt}{AllCnt},数量很多,我们可以考虑直接double存就行了 \]

最后,值域很大,老规矩离散化一下,然后注意第二组 \(dp\) 的第一维偏序,要取反,所以我们取答案的时候也是取反后的下标去取。然后用 stable_sort 可以避免严格偏序问题,可以省一些代码,我个人很多合在一起写了,代码上很简略。当然手写归并数组之类的也行,本处直接调稳定排序函数了。

参照代码
#include <bits/stdc++.h>

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")

#define isPbdsFile

#ifdef isPbdsFile

#include <bits/extc++.h>

#else

#include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope>

#endif

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
typedef __int128 i128;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};

template <typename T>
int disc(T* a, int n)
{
    return unique(a + 1, a + n + 1) - (a + 1);
}

template <typename T>
T lowBit(T x)
{
    return x & -x;
}

template <typename T>
T Rand(T l, T r)
{
    static mt19937 Rand(time(nullptr));
    uniform_int_distribution<T> dis(l, r);
    return dis(Rand);
}

template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
    return (a % b + b) % b;
}

template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
    a %= c;
    T1 ans = 1;
    for (; b; b >>= 1, (a *= a) %= c)if (b & 1)(ans *= a) %= c;
    return modt(ans, c);
}

template <typename T>
void read(T& x)
{
    x = 0;
    T sign = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')sign = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    x *= sign;
}

template <typename T, typename... U>
void read(T& x, U&... y)
{
    read(x);
    read(y...);
}

template <typename T>
void write(T x)
{
    if (typeid(x) == typeid(char))return;
    if (x < 0)x = -x, putchar('-');
    if (x > 9)write(x / 10);
    putchar(x % 10 ^ 48);
}

template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
    write(x), putchar(c);
    write(c, y...);
}


template <typename T11, typename T22, typename T33>
struct T3
{
    T11 one;
    T22 tow;
    T33 three;

    bool operator<(const T3 other) const
    {
        if (one == other.one)
        {
            if (tow == other.tow)return three < other.three;
            return tow < other.tow;
        }
        return one < other.one;
    }

    T3() { one = tow = three = 0; }

    T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
    {
    }
};

template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
    if (x < y)x = y;
}

template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
    if (x > y)x = y;
}

struct Hash
{
    static uint64_t splitmix64(uint64_t x)
    {
        x += 0x9e3779b97f4a7c15;
        x = (x ^ x >> 30) * 0xbf58476d1ce4e5b9;
        x = (x ^ x >> 27) * 0x94d049bb133111eb;
        return x ^ x >> 31;
    }

    static size_t get(const uint64_t x)
    {
        static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
        return splitmix64(x + FIXED_RANDOM);
    }

    template <typename T>
    size_t operator()(T x) const
    {
        return get(std::hash<T>()(x));
    }

    template <typename F, typename S>
    size_t operator()(pair<F, S> p) const
    {
        return get(std::hash<F>()(p.first)) ^ std::hash<S>()(p.second);
    }
};

constexpr int N = 5e4 + 10;
typedef pair<int, double> pid;

inline void Max(pid& x, const pid& y)
{
    if (x.first < y.first)x = y;
    else if (x.first == y.first)x.second += y.second;
}

int idx;
hash2<int, int, Hash> mp;

enum Type
{
    Pre = 0, Suf = 1
};

struct
{
    pid bit[2][N]; //0是前缀最值,1是后缀最值
    void add(int x, const pid& val, const Type& id)
    {
        if (id == Pre)while (x <= idx)Max(bit[id][x], val), x += lowBit(x);
        else while (x)Max(bit[id][x], val), x -= lowBit(x);
    }

    pid query(int x, const Type& id) const
    {
        pid ans;
        if (id == Pre)while (x)Max(ans, bit[id][x]), x -= lowBit(x);
        else while (x <= idx)Max(ans, bit[id][x]), x += lowBit(x);
        return ans;
    }

    void clear(int x, const Type& id)
    {
        constexpr pid zero;
        if (id == Pre)while (x <= idx)bit[id][x] = zero, x += lowBit(x);
        else while (x)bit[id][x] = zero, x -= lowBit(x);
    }
} Bit;

//1.hL>=hR,vL>=vR
//2.hL<=hR,vL<=vR
struct DP
{
    int h, v;
    pid ans;
} dp1[N], dp2[N];

int tmp[N];

inline bool cmp1(const int x, const int y)
{
    return dp1[x].h >= dp1[y].h;
}

inline bool cmp2(const int x, const int y)
{
    return dp2[x].h <= dp2[y].h;
}

inline void cdq(const int L, const int R, const Type type = Suf)
{
    const int mid = L + R >> 1;
    if (L == R)return;
    cdq(L, mid, type);
    auto dp = type == Suf ? dp1 : dp2;
    auto cmp = type == Suf ? cmp1 : cmp2;
    forn(i, L, R)tmp[i] = i;
    stable_sort(tmp + L, tmp + mid + 1, cmp), stable_sort(tmp + mid + 1, tmp + R + 1, cmp);
    int l = L;
    forn(r, mid+1, R)
    {
        while (l <= mid and cmp(tmp[l], tmp[r]))Bit.add(dp[tmp[l]].v, dp[tmp[l]].ans, type), l++;
        auto other = Bit.query(dp[tmp[r]].v, type);
        other.first++;
        Max(dp[tmp[r]].ans, other);
    }
    forn(i, L, l-1)Bit.clear(dp[tmp[i]].v, type);
    cdq(mid + 1, R, type);
}

int n;
set<int> ord;
int h[N], v[N];

inline void solve()
{
    cin >> n;
    forn(i, 1, n)
    {
        cin >> h[i] >> v[i];
        ord.insert(h[i]), ord.insert(v[i]);
    }
    for (const auto v : ord)mp[v] = ++idx;
    forn(i, 1, n)
    {
        h[i] = mp[h[i]], v[i] = mp[v[i]];
        dp1[i] = DP(h[i], v[i], pid(1, 1));
        dp2[n - i + 1] = DP(h[i], v[i], pid(1, 1));
    }
    cdq(1, n, Pre), cdq(1, n, Suf);
    pid ans;
    forn(i, 1, n)Max(ans, dp1[i].ans);
    auto [maxLen,cntAll] = ans;
    cout << maxLen << endl;
    cout << fixed << setprecision(5);
    forn(i, 1, n)
    {
        auto [pre,cnt1] = dp1[i].ans;
        auto [suf,cnt2] = dp2[n - i + 1].ans;
        cout << (pre + suf - 1 == maxLen ? cnt1 * cnt2 / cntAll : 0) << ' ';
    }
}

signed int main()
{
    // MyFile
    Spider
    //------------------------------------------------------
    // clock_t start = clock();
    int test = 1;
    //    read(test);
    // cin >> test;
    forn(i, 1, test)solve();
    //    while (cin >> n, n)solve();
    //    while (cin >> test)solve();
    // clock_t end = clock();
    // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}

\[时间复杂度为:\ O(n\log^2{V_{max}}) \]

posted @ 2024-02-28 23:38  Athanasy  阅读(31)  评论(0编辑  收藏  举报