「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
#include
#include
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;
}