问题
描述 Description
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK,则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式 Input Format
输入的第一行是一个整数N(2 <= N <= 100),表示同学的总数。第一行有n个整数,用空格分隔,第i个整数Ti(130 <= Ti <= 230)是第i位同学的身高(厘米)。
输出格式 Output Format
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
分析
这是个灰常经典的问题。我们有n2的算法,(这里上午写错了)先从左到右和从右到左分别求两次最长上升序列,然后再枚举中间的人,两次的f【k】的值-1就是有的人,最后用总人数减去这个值即可。
为什么一定要这么做,就是为了保证在产生的序列中包含ti这个元素。
反思
经典的问题,经典的模型。但是看上去比较难理解,关键是能看到题目的本质,找到切入点,转化问题。类似的问题还有 过河,导弹拦截……还有输出方案的buylow,总之,多积累,对模型有感觉。
有些时候需要但创造性,比如:给定一个序列让你求包含第k项的最长XX序列。乍一看没思路,其实只需要做预处理即可,将k之前的和之后的不满足条件的元素都删掉,这样就保证了k一定会在最优决策中出现
code
program liukee;
var
a:array[1..1000] of longint;
f1,f2:array[0..1000] of longint;
n,i,k,ans,j:longint;
begin
readln(n);
for i:=1 to n do
read(a[i]);
for i:=1 to n do
begin
f1[i]:=1;
f2[i]:=1;
end;
for i:=1 to n do
for j:=i-1 downto 1 do
if(a[j]<a[i])and(f1[i]<f1[j]+1)then f1[i]:=f1[j]+1;
for i:=n downto 1 do
for j:=i+1 to n do
if(a[j]<a[i])and(f2[i]<f2[j]+1)then f2[i]:=f2[j]+1;
for k:=1 to n do
if f1[k]+f2[k]-1>ans then ans:=f1[k]+f2[k]-1;
writeln(n-ans);
end.
题外话
求最长xx序列的方程可以优化为nlogn的。用到了单调队列和二分查找的方法。
对于每个f[]的取值k,我们只需要保留f[t]=k的所有a[t]值中最小的,令d[k]:=min{a[t]};f[t]=k;
我们注意到决策区间d满足单调性。单调递增
设当前已产生的序列长度为len,先判断a[t]与d[len],如果a[t]>d[len],那么将a[t]接在d[len]后,得到一个更长的序列inc(len),d[len]:=a[t];。
否则在d[1]……d[len]中寻找最大的j满足d[j]<a[t]令k=j+1;再另d[k]:=a[t]。
len即为所求的解
这个方法的难点在于二分查找。注意模板的打法,正确性。向想要的靠近,记住已产生的正确的,再次二分。
code(二分法)
program liukee;
var
a:array[1..10000] of longint;
d:array[1..10000] of longint;
ans,i,j,l,r,n,mid,op:longint;
begin
readln(n);
for i:=1 to n do
read(a[i]);
d[1]:=a[1];
ans:=1;
for i:=2 to n do
begin
if a[i]>=d[ans] then
begin
inc(ans);
d[ans]:=a[i];
end
else begin
l:=1; r:=ans;
while l<=r do
begin
mid:=(l+r)>>1;
if d[mid]<a[i] then begin op:=mid; l:=mid+1;end
else r:=mid-1;
end;
if op=0 then inc(op);
d[op]:=a[i];
end;
end;
writeln(ans);
end.