简单题意

给定 \(n\) 个数对 \((h_i, v_i)\)。

求:

  1. 最长不上升子序列的长度。
  2. 对于每个 \(i\),分别求出包含数对 \((h_i, v_i)\) 的最长上升子序列的个数和最长不上升子序列的个数和的比。

数据范围:\(1 \leq n \leq 5 \times {10} ^ 4\),\(\forall 1 \leq i \leq n, 1 \leq h_i, v_i \leq {10}^9\)。

分析

问题 \(1\)

先考虑 \(O(n^2)\) 做法,本质与一维的相同。

定义 \(f_i\) 表示以数对 \((h_i, v_i)\) 结尾的最长不上升子序列的长度。

那么有

\[\begin{aligned}
f_i & = \max(1, \max\{ f_j + 1 \}), h_i \leq h_j \wedge v_i \leq v_j \\
\end{aligned}
\]

推出了式子后考虑优化,可以使用 \(\texttt{cdq}\) 分治。

对于一个区间 \(\texttt{[l, r]}\) 的转移。

\(\texttt{int mid = (l + r) >> 1}\)

先递归 \(\texttt{cdq(l, mid)}\)。

假设 \(\texttt{[l, mid]}\) 已经求出正确答案,即 \(f_{\texttt{l} \sim \texttt{r}}\) 都是正确的。

考虑如何转移,即 \(\texttt{[l, mid]}\) 对 \(\texttt{[mid + 1, r]}\) 的贡献。

与三维偏序一样,合并 \(h\),以 \(v\) 为下标把 \(f\) 存放在树状数组中。

只不过这里的 \(f\) 需要取最大值。

最后递归 \(\texttt{cdq(mid + 1, r)}\)。

因为是最后递归 \(\texttt{cdq(mid + 1, r)}\),所以询问不能真正合并,在结束时需要还原成原来的顺序。

问题 \(2\)

问题 \(1\) 解决后问题 \(2\) 就简单了。

只需要在树状数组中再维护一个统计个数数组即可。

  • 修改的值大于当前最大值就修改。
  • 修改的值等于当前最大值就累加。

因为求的是 包含 数对 \((h_i, v_i)\) 的最长不上升子序列的个数,所以还需要反着求一遍最长不上升子序列的个数。

两个数相乘再除以总数就是答案,前提是这个数对被包含在至少一个最长不上升子序列中。

温馨提示,个数可能会超过 \(\texttt{long long}\) 的范围,建议使用 \(\texttt{double}\)。

\(\texttt{code}\)

#include <cstdio>
#include <vector>
#include <utility>
#include <iostream>
#include <algorithm> int rint() {
int x = 0, fx = 1;
char c = getchar(); while (c < '0' || c > '9') {
fx ^= (c == '-');
c = getchar();
} while ('0' <= c && c <= '9') {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
} if (!fx) {
return -x;
} return x;
} void read(int &x) {
x = rint();
} template<typename... Ts>
void read(int &x, Ts &...rest) {
read(x);
read(rest...);
} int Max(int u, int v) {
return (u > v) ? u : v;
} int Min(int u, int v) {
return (u < v) ? u : v;
} const int MAX_n = 5e4; int n, Time; // Time 是时间戳优化树状数组,可以不用清空
int dp1[MAX_n + 5]; // 正
int dp2[MAX_n + 5]; // 反
int vis[MAX_n + 5]; // 树状数组时间戳
int Bit[MAX_n + 5];
double bit[MAX_n + 5];
double num1[MAX_n + 5]; // 正
double num2[MAX_n + 5]; // 反 std::vector<int> lsh; // 离散化 v struct Missile {
int idx, h, v;
} q[MAX_n + 5];
Missile tmp[MAX_n + 5]; bool cmph1(Missile x, Missile y) {
return x.h > y.h;
} bool cmph2(Missile x, Missile y) {
return x.h < y.h;
} int lowbit(int x) {
return x & (-x);
} void add(int k, int x, double y) {
while (k <= n) {
if (vis[k] != Time) {
vis[k] = Time;
Bit[k] = x;
bit[k] = y;
} else {
if (x > Bit[k]) {
Bit[k] = x;
bit[k] = y;
} else if (x == Bit[k]) {
bit[k] += y;
}
} k += lowbit(k);
}
} std::pair<int, double> ask(int k) {
int resmax = 0;
double ressum = 0.0; while (k > 0) {
if (vis[k] == Time) {
if (Bit[k] > resmax) {
resmax = Bit[k];
ressum = bit[k];
} else if (Bit[k] == resmax) {
ressum += bit[k];
}
} k -= lowbit(k);
} return std::make_pair(resmax, ressum);
} void merge1(int L1, int R1, int L2, int R2) {
++Time;
int i = L1, j = L2; for (int k = L1; k <= R2; k++) {
tmp[k] = q[k];
} std::sort(q + L1, q + R1 + 1, cmph1);
std::sort(q + L2, q + R2 + 1, cmph1); while (i <= R1 || j <= R2) {
if (i <= R1 && (j > R2 || q[i].h >= q[j].h)) {
add((int)lsh.size() + 1 - q[i].v, dp1[q[i].idx], num1[q[i].idx]);
i++;
} else {
std::pair<int, double> now = ask((int)lsh.size() + 1 - q[j].v); if (now.first + 1 > dp1[q[j].idx]) {
dp1[q[j].idx] = now.first + 1;
num1[q[j].idx] = now.second;
} else if (now.first + 1 == dp1[q[j].idx]) {
num1[q[j].idx] += now.second;
} j++;
}
} for (int k = L1; k <= R2; k++) {
q[k] = tmp[k];
}
} 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);
} void merge2(int L1, int R1, int L2, int R2) {
++Time;
int i = L1, j = L2; for (int k = L1; k <= R2; k++) {
tmp[k] = q[k];
} std::sort(q + L1, q + R1 + 1, cmph2);
std::sort(q + L2, q + R2 + 1, cmph2); while (i <= R1 || j <= R2) {
if (i <= R1 && (j > R2 || q[i].h <= q[j].h)) {
add(q[i].v, dp2[q[i].idx], num2[q[i].idx]);
i++;
} else {
std::pair<int, double> now = ask(q[j].v); if (now.first + 1 > dp2[q[j].idx]) {
dp2[q[j].idx] = now.first + 1;
num2[q[j].idx] = now.second;
} else if (now.first + 1 == dp2[q[j].idx]) {
num2[q[j].idx] += now.second;
} j++;
}
} for (int k = L1; k <= R2; k++) {
q[k] = tmp[k];
}
} void cdq2(int L, int R) {
if (L == R) {
return ;
} int Mid = (L + R) >> 1;
cdq2(L, Mid);
merge2(L, Mid, Mid + 1, R);
cdq2(Mid + 1, R);
} signed main() {
n = rint(); for (int i = 1; i <= n; i++) {
read(q[i].h, q[i].v);
q[i].idx = i;
lsh.push_back(q[i].v);
} std::sort(lsh.begin(), lsh.end());
lsh.resize(std::unique(lsh.begin(), lsh.end()) - lsh.begin()); for (int i = 1; i <= n; i++) {
q[i].v = std::lower_bound(lsh.begin(), lsh.end(), q[i].v) - lsh.begin() + 1;
dp1[i] = dp2[i] = 1;
num1[i] = num2[i] = 1.0;
} cdq1(1, n); for (int i = n / 2; i >= 1; i--) {
std::swap(q[i], q[n + 1 - i]);
} cdq2(1, n);
int res = 0;
double sum = 0; for (int i = 1; i <= n; i++) {
if (dp1[i] > res) {
res = dp1[i];
sum = num1[i];
} else if (dp1[i] == res) {
sum += num1[i];
}
} printf("%d\n", res); for (int i = 1; i <= n; i++) {
if (dp1[i] + dp2[i] - 1 != res) {
printf("0.00000");
} else {
printf("%.5f", 1.0 * num1[i] * num2[i] / sum);
} putchar((i == n) ? '\n' : ' ');
} return 0;
}

「 题解 」P2487 [SDOI2011]拦截导弹的更多相关文章

  1. P2487 [SDOI2011]拦截导弹

    题目 P2487 [SDOI2011]拦截导弹 做\(SDOI\)有种想评黑的感觉,果然还是太弱了 做法 独立写(调)代码三个小时祭 简化题目:求二维最长不上升子序列及每个点出现在最长不上升子序列概率 ...

  2. bzoj 2244: [SDOI2011]拦截导弹 cdq分治

    2244: [SDOI2011]拦截导弹 Time Limit: 30 Sec  Memory Limit: 512 MBSec  Special JudgeSubmit: 237  Solved: ...

  3. 【BZOJ2244】[SDOI2011]拦截导弹(CDQ分治)

    [BZOJ2244][SDOI2011]拦截导弹(CDQ分治) 题面 BZOJ 洛谷 题解 不难发现这就是一个三维偏序+\(LIS\)这样一个\(dp\). 那么第一问很好求,直接\(CDQ\)分治之 ...

  4. [BZOJ2244][SDOI2011]拦截导弹 CDQ分治

    2244: [SDOI2011]拦截导弹 Time Limit: 30 Sec  Memory Limit: 512 MB  Special Judge Description 某国为了防御敌国的导弹 ...

  5. 【LG2481】[SDOI2011]拦截导弹

    [LG2481][SDOI2011]拦截导弹 题面 洛谷 题解 可以看出第一问就是一个有关偏序的\(LIS\),很显然可以用\(CDQ\)优化 关键在于第二问 概率\(P_i=\) \(总LIS数\) ...

  6. BZOJ 2244: [SDOI2011]拦截导弹 DP+CDQ分治

    2244: [SDOI2011]拦截导弹 Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度.并且能够拦截 ...

  7. 「题解」「美团 CodeM 资格赛」跳格子

    目录 「题解」「美团 CodeM 资格赛」跳格子 题目描述 考场思路 思路分析及正解代码 「题解」「美团 CodeM 资格赛」跳格子 今天真的考自闭了... \(T1\) 花了 \(2h\) 都没有搞 ...

  8. 「题解」「HNOI2013」切糕

    文章目录 「题解」「HNOI2013」切糕 题目描述 思路分析及代码 题目分析 题解及代码 「题解」「HNOI2013」切糕 题目描述 点这里 思路分析及代码 题目分析 这道题的题目可以说得上是史上最 ...

  9. 「题解」JOIOI 王国

    「题解」JOIOI 王国 题目描述 考场思考 正解 题目描述 点这里 考场思考 因为时间不太够了,直接一上来就着手暴力.但是本人太菜,居然暴力爆 000 ,然后当场自闭- 一气之下,发现对 60pts ...

随机推荐

  1. CSS基础 元素整体透明效果(包含内容+背景及子元素)

    属性名:opacity:数字+px; 数字值取值0-1之间数字 数字值:1表示完全不透明 0表示完全透明使用后效果 html结构代码 <div class="box"> ...

  2. Inside Java Newscast #1 深度解读

    本文是 Inside Java Newscast #1 的个人体验与解读.视频地址:点击这里 ⎯⎯⎯⎯⎯⎯ Chapters ⎯⎯⎯⎯⎯⎯ 0:00 - Intro 0:57 - Java 16 – ...

  3. 使用.NET 6开发TodoList应用(22)——实现缓存

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 有的时候为了减少客户端请求相同资源的逻辑重复执行,我们会考虑使用一些缓存的方式,在.NET 6中,我们可以借助框架提供的中间件 ...

  4. HTML相关知识入门

    HTML 注意点 html是不区分大小写的 建议包括嵌套,不要交叉嵌套 文件后缀名可以是.html或.htm 格式 <!------类似于java中类的大括号--------> <h ...

  5. Presto 在字节跳动的内部实践与优化

    在字节跳动内部,Presto 主要支撑了 Ad-hoc 查询.BI 可视化分析.近实时查询分析等场景,日查询量接近 100 万条.本文是字节跳动数据平台 Presto 团队-软件工程师常鹏飞在 Pre ...

  6. HashMap的实现原理(看这篇就够了)

    一线资深java工程师明确了需要精通集合容器,尤其是今天我谈到的HashMap. HashMap在Java集合的重要性不亚于Volatile在并发编程的重要性(可见性与有序性). 我会重点讲解以下9点 ...

  7. 安装MySQL到Ubuntu 20.04

    本文的内容主要来自对How To Install MySQL on Ubuntu 20.04的翻译.在根据该文的指导下,我在自己的Ubuntu 20.04.3 LTS版本中安装了MySQL 8. St ...

  8. 集合框架-List集合的常见方法

    1 package cn.itcast.p4.list.demo; 2 3 import java.util.List; 4 import java.util.ArrayList; 5 6 publi ...

  9. 【程序18】求s=a+aa+aaa+aaaa+aa...a的值

    求s=a+aa+aaa+aaaa+aa-a的值,其中a是一个数字.例如2+22+222+2222+22222(此时共有5个数相加),几个数相加由键盘控制. 知识点:在Python 3里,reduce( ...

  10. Redis Hyperloglog的原理及数学理论的通俗理解

    redis中有一种数据格式,hyperloglog,本文就此数据结构的作用.redis的实现及其背后的数学原理作一个整理.当然本文不包含任何数学公式,而是希望用直观的例子帮大家理解. 主要内容如下: ...