题目
给定一个长为n(1≤n≤100000)的正整数(最大为2^31-1)序列,求最长不下降子序列的长度.
Sample inupt
7
1 3 3 6 8 3 6
Sample output
5
博主自己随便弄的一道题目
这里我讲四种方法
时间复杂度O(n^2),用在此题会Tle,普通DP在本博文视为暴力~~
dp[i]表示以第i个数结尾的最长长度
我今天改进了码风,码这个代码就当练码风了
Code:
uses math;
var
dp,a:array[0..100000] of longint;
n,i,j,ans:longint;
begin
readln(n);
for i := 1 to n do
begin
read(a[i]);
for j := 1 to i-1 do
if a[j] <= a[i] then dp[i] := max(dp[i], dp[j]);
inc(dp[i]);
ans := max(ans, dp[i]);
end;
writeln(ans);
end.
我们令d[i]表示长度为i的最长不下降子序列的最后一个数的最小值
发现:d数组具有单调性
具体如何操作呢?
令len为当前最长长度
每读入一个数x,x有两种情况:
外面一个1~n循环,里面一个logn的二分,时间复杂度O(nlogn)
Code:
var
d:array[0..1000000] of longint;
n,x,y,len,i:longint;
function find:longint;
var
l,r,mid:longint;
begin
l := 0; r := len; find := maxlongint;
while l <= r do
begin
mid := (l + r) >> 1;
if d[mid] > x then
begin
r := mid - 1; find := mid;
end else l := mid + 1;
end;
end;
begin
readln(n);
read(x);
len := 1;
d[1] := x;
for i := 2 to n do
begin
read(x);
if d[len] <= x then
begin
inc(len);
d[len] := x;
end else
begin
y := find;
if y <> maxlongint then d[y] := x;
end;
end;
writeln(len);
end.
树状数组的解法是优化O(n^2)暴力的,目标是把一个n变成logn。
一般树状数组优化dp的话dp数组的定义是不变的,但这边要稍微变一下,令tree[i]表示末尾为i(并不是第i个数)的最长长度
所以时间复杂度也变了,令maxv为序列中元素最大值
则O(nlog maxv)
Code:
uses math;
const
maxn = 10000000;
var
tree:array[0..maxn] of longint;
n,i,x,sum,ans:longint;
function lowbit(x:longint):longint;
begin
exit(x and -x);
end;
procedure change(x,y:longint);
begin
while x <= maxn do
begin
tree[x] := max(tree[x], y);
inc(x, lowbit(x));
end;
end;
function getsum(x:longint):longint;
begin
getsum := 0;
while x > 0 do
begin
getsum := max(getsum, tree[x]);
dec(x, lowbit(x));
end;
end;
begin
readln(n);
for i := 1 to n do
begin
read(x);
sum := getsum(x)+1;
change(x,sum);
ans := max(ans, sum);
end;
writeln(ans);
end.
跟树状数组一样的操作,复杂度同上
Code:
uses math;
const
maxn= 10000000;
var
tree:array[0..maxn] of record
num,tag:longint;
end;
n,i,x,sum,ans:longint;
procedure pushup(root:longint);
begin
tree[root].num := max(tree[root << 1].num,tree[root << 1 + 1].num);
end;
procedure pushdown(root:longint);
begin
if (tree[root].tag > 0) then
begin
tree[root << 1].num := max(tree[root << 1].num, tree[root].tag);
tree[root << 1 + 1].num := max(tree[root << 1 + 1].num, tree[root].tag);
tree[root << 1].tag := max(tree[root << 1].tag, tree[root].tag);
tree[root << 1 + 1].tag := max(tree[root << 1 + 1].tag,tree[root].tag);
tree[root].tag := 0;
end;
end;
procedure update(root,tl,tr,l,r,k:longint);
var
mid:longint;
begin
if (tl > r) or (tr < l) then exit;
if (tl >= l) and (tr <= r) then
begin
tree[root].num := max(tree[root].num, k);
tree[root].tag := max(tree[root].tag, k);
exit;
end;
pushdown(root);
mid := (tl + tr) >> 1;
update(root << 1, tl, mid, l, r, k);
update(root << 1 + 1, mid + 1, tr, l, r, k);
pushup(root);
end;
function query(root,tl,tr,l,r:longint):longint;
var
mid:longint;
begin
if (tl > r) or (tr < l) then exit(0);
if (tl >= l) and (tr <= r) then exit(tree[root].num);
pushdown(root);
mid := (tl + tr) >> 1;
query := max(query(root << 1, tl, mid, l, r), query(root << 1 + 1, mid + 1, tr, l, r));
end;
begin
readln(n);
for i := 1 to n do
begin
read(x);
sum := query(1, 1, n, 1, x) + 1;
update(1, 1, n, x, maxn, sum);
ans := max(ans, sum);
end;
writeln(ans);
end.
单调队列,树状数组,线段树这三种方法都≈O(nlogn)
但仔细观察一下3、4两种方法和题目的数据范围可发现,树状数组和线段树在本题不能用!因为序列中元素最大可以达到2^31-1
在本题四种方法提交后的结果:
令maxn为n的最大值(与代码中的maxn无关)
我们发现:
经过分析,我们得到一个结论:这种题型用单调队列!
我码字码到这里突然又发现:O(nlogn)算法无法打印具体的方案(即输出最长的不下降子序列)!!!
若题目要求打印方案之类的东西,只能用O(n^2)
所以,现在真正的结论出来了:
今天闲来无事随便翻了翻我的博客,发现这篇还是有些漏洞
其实,我上面说的部分东西是错误的,如果你上面看下来产生了疑问,那么我这里解答一下
针对不打印方案的最长不下降子序列(因为现在没有什么题目这么无聊,让你把方案给输出),还是有比较多的题目用到了最长不下降这个模型的,我们可以采用三种方法
单调队列最直接,树状数组和线段树我是傻了,数值太大,n又不大,离散一下不就行了吗。
好了,三种方法皆可食用,但我更倾向于单调队列和树状数组,树状数组在这里比较灵活,可以处理很多问题,而线段树码量原因就不考虑了