在翻dp水题的时候找到的有趣的题0v0
原文>>https://www.luogu.org/problem/show?pid=2362<<
题目描述
某农场有一个由按编号排列的n根木桩构成的首尾不相连的围栏。现要在这个围栏中选取一些木桩,按照原有的编号次序排列之后,这些木桩高度成一个升序序列。所谓的升序序列就是序列中的任何一个数都不小于它之前的任何一个数。试编写程序从这个围栏中选取合适的木桩使得选出的木桩个数t最大,并求出选取出t根木桩的方案总数c。
输入输出格式
输入格式:
文件中的第一行只有一个数m,表明随后有m个问题的描述信息。每个问题的描述信息格式为n h1,h2,h3,…,hn(其中hi(i=1,2,3,…,n)表示第i根木桩的高度。)
输出格式:
依次输出每个问题中t和c的解。每行输出一个问题的解。
这题和一般最长不下降子序列的区别就在于他需要输出最大解的种类个数.怎么统计这些可能解就是这题有意思的地方.
我们通过a数组存序列,b来记录前i个数的最长子序列,最关键的是c,c[i]的意义是记录在1~i-1中满足最大序列为b[i]-1的子序列个数.
挺难理解,我举个例子:
给8个数字 17534851 当i=3时 b[3]=2 (15或17) c[3]则表示当i=1~2中满足b[i]=1的情况 显然只有i=1时(i=2时b[2]=2) 所以c[3]=1;
i=4时 b[4]=2 c[4]=1;i=5时 b[5]=3 (134) c[5]=3 (i=2,3,4时c[i]为2).
于是乎,i=n时最长序列就在b里找,假设找到最大值bmax,而可能的情况则为 for(i=1;i<=n;i++) if(b[i]=bmax) ans+=c[i];
为什么会有这样的关系?我们在开始时先给 b c 数组赋初值1(序列最短可为自己本身),之后的讨论都是建立在a[i]>=a[r]上的.所以要在第一个循环前加上判断条件if(a[i]>=a[r]) (r的出处请脑补最长不下降子序列模板),bmax对应的c[i]意为不包括a[i]在内的最大序列为bmax-1的情况.所以如果在尾部加上a[i],最长数目就变成了bmax.这就是该算法的原理.觉得理解不充分的话可以自己举一个例子套套看,与代码结合也许会有助于理解.
#include<iostream> using namespace std; int main() { int k,n,b[2005],c[2005],a[2005],i,j; cin>>k; while(k--) { cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=c[i]=1; } for(j=2;j<=n;j++) { int r; for(r=j-1;r>=1;r--) { if(a[j]>=a[r]) { if(b[j]<b[r]+1) { b[j]=b[r]+1; c[j]=c[r]; } else if(b[j]==b[r]+1) { c[j]+=1; } } } } int max=-1,bj=0; for(i=1;i<=n;i++) { if(b[i]>max) { max=b[i]; bj=i; } } int ans=0; for(i=1;i<=n;i++) { if(b[i]==b[bj]) ans+=c[i]; } cout<<b[bj]<<" "<<ans<<endl; } }