「Solution」[SDOI2011] 拦截导弹

Problem

Link

Solution

看到第一问,不难想到是个有两个属性的 \(\text{LIS}\) 问题,很容易想到 \(\text{O(}\) \(n^2\) \(\text{)}\) 的暴力 DP 。

定义 \(\text{dp[i]}\) 为以当前导弹结尾的最多拦截数,\(\text{O(}\) \(n^2\) \(\text{)}\) 转移。

这个问题有三个要求 : \(id_i < id_j,h_i \ge h_j,v_i \ge v_j\) ,是个三维偏序问题,可以用 CDQ 来优化 ta。

CDQ 求解问题是将一个区间分为左右两个小区间,先求解两个区间的子问题,再求跨区间的情况。

但对于 DP 转移来说,若按照正常的顺序,则在求解右边的子区间时,右边的每个 DP 的值并没有得到。

所以在求完左区间的子问题后,要先将 DP 从左区间转移到右区间,再求解右区间。


再看到第二问,求在拦截的导弹最多的情况下拦截当前导弹的概率,即是拦下当前导弹的方案数除以总方案数。

直接求出拦下当前导弹的方案数比较困难,不妨将其拆分成两个子问题:以当前导弹结尾的方案数、以当前导弹开始的方案数。而总方案数就等于两子问题方案数的乘积。

而方案数的转移可以与第一问中的最多的拦截数一起转移。两种情况用两个 CDQ 处理就行了。

Code

有些细节,调的挺久的。。。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define LL long long
#define rep(i, j, k) for(int i = (j); i <= (k); i ++)
#define per(i, j, k) for(int i = (j); i >= (k); i --)

const int Maxn = 5e4;

int n;
int Bit[Maxn + 5], dp1[Maxn + 5], dp2[Maxn + 5];
double Bitf[Maxn + 5], fL[Maxn + 5], fR[Maxn + 5], f[Maxn + 5];

namespace BIT {
	void Update (int x, int y, double f) {
		for (int i = x; i <= n; i += i & -i) {
            if (y > Bit[i]) Bit[i] = y, Bitf[i] = f; //最大值更新后,对应的方案数也要更新,注意是等号
            else if (y == Bit[i]) Bitf[i] += f; //与最大值相同,则将方案数累加
        }
	}
	int Get_Max (int x) {
		int maxn = 0;
		for (int i = x; i; i -= i & -i) maxn = max (Bit[i], maxn);
		return maxn;
	}
    double Get_Sum (int x, int dp) {
        double sum = 0;
        for (int i = x; i; i -= i & -i) {
            if (Bit[i] == dp) sum += Bitf[i]; //最大值等于当前 dp 值的才产生贡献
        }
        return sum;
    }
    void Clear (int x) {
        for (int i = x; i <= n; i += i & -i) Bit[i] = Bitf[i] = 0;
    }
    void Update2 (int x, int y, double f) {
		for (int i = x; i; i -= i & -i) {
            if (y > Bit[i]) Bit[i] = y, Bitf[i] = f;
            else if (y == Bit[i]) Bitf[i] += f;
        }
	}
	int Get_Max2 (int x) {
		int maxn = 0;
		for (int i = x; i <= n; i += i & -i) maxn = max (Bit[i], maxn);
		return maxn;
	}
    double Get_Sum2 (int x, int dp) {
        double sum = 0;
        for (int i = x; i <= n; i += i & -i) {
            if (Bit[i] == dp) sum += Bitf[i];
        }
        return sum;
    }
    void Clear2 (int x) {
        for (int i = x; i; i -= i & -i) Bit[i] = Bitf[i] = 0;
    }
}
using namespace BIT;

struct Node {
    int t, h, v; //t:时间(输入顺序) h,v同题目
    int dpL, dpR; //以 i 结尾/开头 的最长长度
    double fL, fR; //。。。 的方案数
} a[Maxn + 5];

bool CmpH1 (Node x, Node y) {
    return x.h == y.h ? (x.v == y.v ? x.t < y.t : x.v > y.v) : x.h > y.h;
}

bool CmpH2 (Node x, Node y) {
    return x.h == y.h ? (x.v == y.v ? x.t > y.t : x.v < y.v) : x.h < y.h;
}

bool CmpV1 (Node x, Node y) {
    return x.v > y.v;
}

bool CmpV2 (Node x, Node y) {
    return x.v < y.v;
}

void Merge (int l1, int r1, int l2, int r2) {
    sort (a + l1, a + 1 + r1, CmpV1);
    sort (a + l2, a + 1 + r2, CmpV1); //按 v 排序,让 a 中的 v 具有单调性
    int i = l1, j = l2;
    for ( ; j <= r2; j ++) {
        while (i <= r1 && a[i].v >= a[j].v) {
            Update (a[i].t, a[i].dpL, a[i].fL); //满足 v、h
            i ++;
        }
        int New = Get_Max (a[j].t) + 1; //这次的最优即为还满足 t 的偏序条件的长度中的最大值 + 1
        if (a[j].dpL < New) { //若更大,就都更新
            a[j].dpL = New;
            a[j].fL = Get_Sum (a[j].t, New - 1);
        } else if (a[j].dpL == New) { //若相等,则累加
            a[j].fL += Get_Sum (a[j].t, New - 1);
        }
    }
    rep (k, l1, i - 1) Clear (a[k].t); //记得清空
    sort (a + l2, a + 1 + r2, CmpH1); //排回以 h 为关键字的顺序
}

void Cdq (int l, int r) {
    if (l == r) return ;
    int mid = (l + r) >> 1; 
    Cdq (l, mid);
    Merge (l, mid, mid + 1, r);
    Cdq (mid + 1, r);
}

void Merge1 (int l1, int r1, int l2, int r2) { //同上,基本与上面是相反的
    sort (a + l1, a + 1 + r1, CmpV2);
    sort (a + l2, a + 1 + r2, CmpV2);
    int i = l1, j = l2;
    for ( ; j <= r2; j ++) {
        while (i <= r1 && a[i].v <= a[j].v) {
            Update2 (a[i].t, a[i].dpR, a[i].fR);
            i ++;
        }
        int New = Get_Max2 (a[j].t) + 1;
        if (a[j].dpR < New) {
            a[j].dpR = New;
            a[j].fR = Get_Sum2 (a[j].t, New - 1);
        } else if (a[j].dpR == New) {
            a[j].fR += Get_Sum2 (a[j].t, New - 1);
        }
    }
    rep (k, l1, i - 1) Clear2 (a[k].t);
    sort (a + l2, a + 1 + r2, CmpH2);
}

void Cdq1 (int l, int r) {
    if (l == r) return ;
    int mid = (l + r) >> 1; 
    Cdq1 (l, mid);
    Merge1 (l, mid, mid + 1, r);
    Cdq1 (mid + 1, r);
}

int main () {
    scanf ("%d", &n);

    rep (i, 1, n) {
        scanf ("%d %d", &a[i].h, &a[i].v);
        a[i].t = i;
        a[i].dpL = a[i].dpR = 1; //赋初值~
        a[i].fL = a[i].fR = 1;
    }

    sort (a + 1, a + 1 + n, CmpH1);//事先按 h 排序,则在 cdq 中,大区间的左子区间中的每一个都与右子区间中的满足 h 的偏序关系

    Cdq (1, n);

    rep (i, 1, n) dp1[a[i].t] = a[i].dpL, fL[a[i].t] = a[i].fL;

    sort (a + 1, a + 1 + n, CmpH2);

    Cdq1 (1, n);

    rep (i, 1, n) dp2[a[i].t] = a[i].dpR, fR[a[i].t] = a[i].fR;

    int ans = 0;
    double tot = 0;
    rep (i, 1, n) {
        ans = max (ans, max (dp1[i], dp2[i]));
    }
    rep (i, 1, n) {
        if (dp1[i] == ans) tot += fL[i];
    }
    printf ("%d\n", ans);
    rep (i, 1, n) {
        f[i] = fL[i] * fR[i] / tot;
        if (dp1[i] + dp2[i] - 1 == ans) printf ("%.5lf ", f[i]); //记得判一下
        else printf ("0.00000 ");
    }
	return 0;
}
posted @ 2022-02-12 21:03  PoisonNNN  阅读(69)  评论(2编辑  收藏  举报