南理第八届校赛同步赛-F sequence//贪心算法&二分查找优化
题目大意:求一个序列中不严格单调递增的子序列的最小数目(子序列之间没有交叉)。
这题证明贪心法可行的时候,可以发现和求最长递减子序列的长度是同一个方法,只是思考的角度不同,具体证明并不是很清楚,这里就给出贪心法的解题过程。
首先很容易想到的就是对n长度数列进行n次遍历,每一次尽可能长地取出一个递增序列,显然这样最后取出的序列数目是最少的。但是这是一个n^2的算法,如果数据取极端的完全递减情况,很容易就能卡掉时间。Ps:这题的测试数据可能设计的并不是很严谨,这个简单的贪心法只要开一个记录已经取出序列的数组进行优化,就能AC了(而且更让人无语的是南理的官方题解竟然没用二分优化,用了个n^2的算法)。
既然n次遍历数组太慢,那就得寻求一次遍历就能让所有元素都进入一个序列的方法。首先,若当前加入的元素,比前面某个元素小,那么这个元素必定能加入某个序列而不用自成一个序列,这样能保证序列尽可能的少。那么问题来了,并不是随便就能加进一个序列,比如前面有一个子序列3,5,6,现在进来一个元素4,虽然前面有个更小的3,但是这样会相应地拆散前面的子序列,并没有达到减少一个序列产生的目的。思考到这,应该不难想到,后面的元素能否加入前面的序列应当只和子序列的最大项有关,例如3,5,6这个序列,如果进来的元素是7,那么就可以增长序列为3,5,6,7。
已经想到需要维护一个各序列的最大项的数组,那么现在面临的问题是在多个序列都可以加进当前元素的时候,往哪个序列加是最优的决策。(接下来是贪心法的核心思想)可以想象,如果前面已经有若干个序列序列,最大项分别是4,6,15,而当前需要加入的元素是20。发现,三个序列都能加进去,但是如果直接加入4为最大项的序列,那就意味着这个序列的最大项更新成20,变成20,6,15。那么,如果后面再进来一个元素5,发现前面的子序列没有一个能加进去,如果上一步把20加入了6或15,那就意味着现在的元素5是可以加入子序列,而不用产生新的序列。思考到这,贪心决策已经非常明显了:如果当前元素能加入已有的序列,那么应该加入当前元素大于的最大序列(通俗来讲就是最大项比当前元素小,但是离得又是最近的子序列)。给出一个例子作为演示:
现在有序列5 7 10 3 1 8 4 6 9 2。
1、进入5,由于没有已经存在子序列,自成序列:5。
2、进入7,搜索已有的比7小的最大序列,找到了5,更新:7。
3、进入10,同上一步,更新序列:10。
4、进入3,同上一部搜索,未找到符合条件的序列,自成一列:10 3。
5、进入1,同上自成一列:10 3 1。
6、进入8,搜索找到符合条件的3,更新序列:10 8 1。
7、进入4,同上,更新序列:10 8 4。
8、进入6,同上,更新序列:10 8 6。
9、进入9,同上,更新序列:10 9 6。
10、进入2,未找到符合条件的序列,自成一列:10 9 6 2。
程序结束,得到4个子序列分别是:5 7 10,3 8 9,1 4 6,2。
通过上面的模拟程序过程不难发现,其实维护子序列最大项就是一个单调栈,而且每一次对子序列的更新由于贪心决策,并不会破坏其单调的性质,所以这里就有了优化,检索符合条件的子序列时可以使用二分查找,最终程序的复杂度T(n) = O(n*logn)。
附上C++代码(并不是AC代码,为测试做过改动,能输出各个子序列)
#include <cstdio>
#define FOR(i,x,y) for(int i = x; i <= y; ++i)
int t,n,top;
int pre[10010];
int ret[10010];
struct node
{
int data;
int index; //结构体加入index,用于逆向检索,生成序列;
}a[10010];
node temp;
int Find(int l,int r) //二分查找符合的子序列;
{
int mid = (l + r) >> 1;
while(l < r)
{
if(temp.data > a[mid].data)
r = mid;
else if(temp.data < a[mid].data)
l = mid+1;
else return
mid;
mid = (l + r) >> 1;
}
return l;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
scanf("%d",&a[1].data);
a[1].index = 1;
ret[1] = a[1].data;
top = 1;
FOR(i,0,n)
pre[i] = i;
FOR(i,2,n)
{
scanf("%d",&temp.data);
temp.index = i;
ret[i] = temp.data;
if(a[top].data > temp.data) //如果比栈顶元素小,就直接压栈;
{
++top;
a[top] = temp;
}
else
{
int cnt = Find(1,top); //检索后,更新序列;
pre[i] = a[cnt].index;
a[cnt] = temp;
}
}
printf("%d\n",top); //最后栈的长度就是序列数;
for(int i = 1; i <= top; ++i) //倒序输出所有序列,要正序需要数组或者递归输出;
{
int k = a[i].index;
while(pre[k] != k)
{
printf("%d ",ret[k]);
k = pre[k];
}
printf("%d\n",ret[k]);
}
}
return 0;
}
南理第八届校赛同步赛-F sequence//贪心算法&二分查找优化的更多相关文章
- 南理第八届校赛同步赛-C count_prime//容斥原理
大致思路就是先求出n的质因数假设是a1-an,然后在1-a的区间里面查找至少能整除{a1,a2...an}中一个元素的数有多少个,对1-b也做相同的处理,而找出来的元素肯定是与n不互质的,那么把区间的 ...
- 哈尔滨理工大学软件与微电子学院第八届程序设计竞赛同步赛(高年级) Solution
A: Solved. 分别处理出每个%7后余数的数字个数,再组合一下 #include <bits/stdc++.h> using namespace std; #define ll lo ...
- 山东省ACM多校联盟省赛个人训练第六场 poj 3335 D Rotating Scoreboard
山东省ACM多校联盟省赛个人训练第六场 D Rotating Scoreboard https://vjudge.net/problem/POJ-3335 时间限制:C/C++ 1秒,其他语言2秒 空 ...
- NOI Day1线上同步赛梦游记
Preface 第一次体验NOI,虽然不是正式选手,但是打打同步赛还是挺涨姿势的,也算是体验了一把. Day1很爆炸,一方面是NOI题目的难度高于自身的水平,另一方面也出现了比较大的失误,T1一个数组 ...
- NOI 2018网络同步赛(游记?)
刚中考完那段时间比较无聊,报名了一个同步赛,报完名才发现成绩单是要挂到网上的,而且因为报的早给了一个很靠前的考号...那布星啊,赶紧学点东西,于是在一周内学了网络流,Treap以及一些数论. Day1 ...
- 10.17(山东多校联合模拟赛 day1)
山东多校联合模拟赛 day1 题不难 rect [问题描述] 给出圆周上的 N 个点, 请你计算出以这些点中的任意四个为四个角,能构成多少个矩形. 点的坐标是这样描述的, 给定一个数组 v[1..N] ...
- 【NOI 2019】同步赛 / 题解 / 感想
非常颓写不动题怎么办…… 写下这篇博客警示自己吧…… 游记 7.16 我并不在广二参加 NOI,而是在距离广二体育馆一公里远的包间打同步赛(其实就是给写不动题找个理由) 上午身体不舒服,鸽了半天才看题 ...
- 2021NOI同步赛
\(NOI\) 网上同步赛 明白了身为菜鸡的自己和普通人的差距 DAY1 \(T1\) 轻重边 [题目描述] 小 W 有一棵 \(n\) 个结点的树,树上的每一条边可能是轻边或者重边.接下来你需要对树 ...
- NOI2021 同步赛游记
写在前面的话 为什么叫游记呢?因为我第一天是在划水中度过的,错过了对原题的发现. O n e I n D a r k \tt OneInDark OneInDark 无比风光地去了浙江,却倒霉地遇上了 ...
随机推荐
- FireMonkey隐藏任务栏图标
FMX(FireMonkey)可以轻松实现很多VCL无法或难以实现的特效,所以将FMX程序作为界面,打包入DLL由VCL程序调用,是一个不错的方案.为了程序的完整性,你不想看见FMX程序在任务栏上显示 ...
- poj 2186 Popular Cows【tarjan求scc个数&&缩点】【求一个图中可以到达其余所有任意点的点的个数】
Popular Cows Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 27698 Accepted: 11148 De ...
- python手记(26)
#!/usr/bin/env python import cv2 import sys fn="test2.jpg" if __name__ == '__main__': prin ...
- [Spark] Pair RDD常见转化操作
本篇博客中的操作都在 ./bin/pyspark 中执行. 对单个 Pair RDD 的转化操作 下面会对 Pair RDD 的一些转化操作进行解释.先假设我们有下面这些RDD(在pyspark中操作 ...
- PHP文件的上传下载
一.文件的上传 1.客户端设置: (1).在 标签中将enctype和method两个属性指明相应的值. Enctype="multipart/form-data"; Method ...
- mysql 配置参数
mysql JDBC Driver 常用的有两个,一个是gjt(Giant Java Tree)组织提供的mysql驱动,其JDBC Driver名称(JAVA类名)为:org.gjt.mm.mysq ...
- json.net xml转换为json格式时,如何将指定节点转换成数组
using System.Xml.Linq; using Newtonsoft.Json; Response.ContentType = "application/json"; X ...
- OpenGL中的投影使用
OpenGL中的投影使用 在OpenGL中,投影矩阵指定了可视区域的大小和形状.对于正投影与透视投影这两种不同的投影类型,它们分别有各自的用途. 正投影 它适用于2D图形,如文本.建筑画图等.在它的应 ...
- [Usaco2006 Nov]Corn Fields牧场的安排 壮压DP
看到第一眼就发觉是壮压DP 然后就三进制枚举子集吧. 这题真是壮压入门好题... 对于dp[i][j] 表示第i行,j状态下前i行的分配方案数. 那么dp[i][j]肯定是从i-1行转过来的 那么由于 ...
- java数组 数组工具类Arrays
一.数组 1.java有严格的数据类型限制,一个数组只能声明一个数据类型,存放同一种数据类型. 2.虽然只能存放一种数据类型,假设A , 如果数据类型B 继承A,依然能存放进入数组. 3.数组的初始化 ...