dp好题--2018.10.16模拟赛T1

题目大意:
多组数据,先给 t t t表示数据组数,给一个 1 1 1 n n n的排列,问是否可以变成一个上升序列和一个下降序列拼起来,并输出上升序列的长度以及这个上升序列,下降序列同理。(语文不太好···
给个样例就是:
输入:
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 &lt; = 50 , t&lt;=50, t<=50, n &lt; = 100000 n&lt;=100000 n<=100000

solution:
第一眼看到这个数据范围就想到了贪心什么的···
反正差不多是 O ( N ) O(N) O(N)做,也想过 d p dp dp,但感觉用一般的 d p dp dp很不现实
所以就想贪心,但是这个题贪心是错的
真的是 d p dp dp啊我还是太 n a i v e naive naive

但是要考虑 d p dp dp状态如何设计,果然不是一般的 d p dp dp状态
也不是 l i s lis lis不要想多了

d p [ i ] dp[i] dp[i]表示以 i i i为上升序列的结尾,最大的下降序列结尾(值)是什么

可以这样设计是因为,已经知道了这个序列一定是由上升和下降序列构成的,所以只要考虑一个的状态,在这个状态下让另一个更优就一定会找到可行解。

然后考虑转移,首先他能 O ( n 2 ) 做 , O(n^2)做, O(n2)有一个 O ( n l o g n ) O(nlogn) O(nlogn)的方法就是线段树优化前缀最大值 e m m m emmm emmm十分暴力很卡常
一个很厉害的 O ( n ) d p O(n)dp O(n)dp就是考虑大力分类讨论, p r e [ i ] pre[i] pre[i]记录上升序列的前一个的下标方便输出,还要记录一个 m v mv mv表示如果 i − 1 i-1 i1 i i i不在一个上升序列时前面可以接上 i i i

这么说不太直观还是看代码吧 q w q qwq qwq

#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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值