dp好題--cj集訓2018.10.16T1
阿新 • • 發佈:2018-12-15
題目大意: 多組資料,先給表示資料組數,給一個到的排列,問是否可以變成一個上升序列和一個下降序列拼起來,並輸出上升序列的長度以及這個上升序列,下降序列同理。(語文不太好··· 給個樣例就是: 輸入: 3 5 5 1 4 2 3 5 1 2 3 5 4 1 1 輸出: YES 2 1 2 3 5 4 3 YES 3 1 2 3 2 5 4 YES 0 1 1
資料範圍:
solution: 第一眼看到這個資料範圍就想到了貪心什麼的··· 反正差不多是做,也想過,但感覺用一般的很不現實 所以就想貪心,但是這個題貪心是錯的 真的是啊我還是太了
但是要考慮狀態如何設計,果然不是一般的狀態 也不是不要想多了
設表示以為上升序列的結尾,最大的下降序列結尾(值)是什麼
可以這樣設計是因為,已經知道了這個序列一定是由上升和下降序列構成的,所以只要考慮一個的狀態,在這個狀態下讓另一個更優就一定會找到可行解。
然後考慮轉移,首先他能有一個的方法就是線段樹優化字首最大值十分暴力很卡常 一個很厲害的就是考慮大力分類討論,記錄上升序列的前一個的下標方便輸出,還要記錄一個表示如果和不在一個上升序列時前面可以接上的
這麼說不太直觀還是看程式碼吧
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 100005
using namespace std;
int t,n,a[maxn],dp[maxn],pre[maxn]; bool used[maxn];
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
int main(){
freopen("quin.in","r",stdin);
freopen("quin.out","w",stdout);
t=rd();
while(t--){
n=rd();
for(int i=1;i<=n;i++) a[i]=rd();
a[n+1]=n+2; dp[0]=n+1;
int mv=0;
for(int i=1;i<=n+1;i++){
dp[i]=-1;//記得初始化!
if(a[i-1]<a[i]) dp[i]=dp[i-1],pre[i]=i-1;//i和i-1組成上升
if(mv!=-1 && a[mv]<a[i] && a[i-1]>dp[i]) dp[i]=a[i-1],pre[i]=mv;//i和mv組成上升
if(i!=1 && a[i-1]<a[i]) mv=-1;//重新找上升的結尾
if(dp[i-1]>a[i] && (mv==-1||a[mv]>a[i-1]))
mv=i-1;//i可以作為下降的結尾,i-1為上升結尾
}
if(dp[n+1]==-1){
puts("NO"); continue;
}
memset(used,0,sizeof used);
puts("YES");
int u=n+1,tot=0;
while(u!=0) used[u]=1,u=pre[u];//找出上升的
for(int i=1;i<=n;i++)
if(used[i]) tot++;
printf("%d ",tot);
for(int i=1;i<=n;i++)
if(used[i]) printf("%d ",a[i]);
printf("\n%d ",n-tot);
for(int i=1;i<=n;i++)
if(!used[i]) printf("%d ",a[i]); printf("\n");
}
return 0;
}