【原题】【noip 2004 T3】【动态规划】合唱队型
问题
描述 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.