1. 程式人生 > >dp好題--cj集訓2018.10.16T1

dp好題--cj集訓2018.10.16T1

題目大意: 多組資料,先給tt表示資料組數,給一個11nn的排列,問是否可以變成一個上升序列和一個下降序列拼起來,並輸出上升序列的長度以及這個上升序列,下降序列同理。(語文不太好··· 給個樣例就是: 輸入: 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

資料範圍:t<=50,t<=50, n<=100000n<=100000

solution: 第一眼看到這個資料範圍就想到了貪心什麼的··· 反正差不多是O(N)

O(N)做,也想過dpdp,但感覺用一般的dpdp很不現實 所以就想貪心,但是這個題貪心是錯的 真的是dpdp啊我還是太naivenaive

但是要考慮dpdp狀態如何設計,果然不是一般的dpdp狀態 也不是lislis不要想多了

dp[i]dp[i]表示以ii為上升序列的結尾,最大的下降序列結尾(值)是什麼

可以這樣設計是因為,已經知道了這個序列一定是由上升和下降序列構成的,所以只要考慮一個的狀態,在這個狀態下讓另一個更優就一定會找到可行解。

然後考慮轉移,首先他能O(n2)O(n^2)做,有一個O(nlogn)O(nlogn)

的方法就是線段樹優化字首最大值emmmemmm十分暴力很卡常 一個很厲害的O(n)dpO(n)dp就是考慮大力分類討論,pre[i]pre[i]記錄上升序列的前一個的下標方便輸出,還要記錄一個mvmv表示如果i1i-1ii不在一個上升序列時前面可以接上ii

這麼說不太直觀還是看程式碼吧qwqqwq

#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; }