题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6592
题目大意:给你一个序列,让你构造一个字典序最大和字典序最小的单峰最长序列(这个单峰序列在峰点之前和峰点之后是严格单调的),并输出出来。
分析:由于峰点范围是[1,k](话说比赛时看成了(1,k),直接就想不下去了),倒序遍历一边可以处理出 f[i][0] 表示选i号点[i,n]的最长单减序列的长度,再倒序遍历一遍类似的转移就可以处理出f[i][1] :i 号点比选,[i,n]的最长单峰子序列,也就是以 i 为起点的最长单峰子序列。
考虑如何转移:初始时让f[i][1] = f[i][0],表示以 i 点为峰点,倒序遍历,寻找j <- [i + 1,n]范围内 a[j] > a[i] 的所有值,f[i][1] = max(f[i][1],f[j][1] + 1)。后面这个过程可以用线段树优化,直接找[i + 1,n]内f[j][1]的最大值来转移。
类似的,也可以处理出g[i][0]:以 i 点为终点 [1,i]的最长单调上升子序列长度;g[i][1] 以 i点为终点 [1,i]的最长单峰子序列长度。
处理出这些有什么用?我们可以按下标顺序把所有的最长单峰子序列的答案按阶段分别存在一个桶内,然后贪心构造最大字典序的答案和最小字典序的答案。
通过枚举峰点可以找出最长单峰子序列的长度 res
若一个点满足 :
f[i][0] + g[i][1] - 1 == res
g[i][0] + f[i][1] - 1 == res
则这个点必然是最长单峰子序列中答案的某一个点,观察可知这两种条件分别代表一个 i 点在峰点的左边和峰点的右边。开两个桶,若在峰点左边,则将下标压入 L[g[i][0]],在右半边则将下标压入R[f[i][0]]中,后面会讲为什么要压入桶内。
根据转移的性质,L桶内和R桶内按下标排序后的值一定是单调的(否则不可能在同一个桶内)
一个单峰子序列有左右两部分,左边表示是单调递增的那一部分序列,右边是单调递减的那一部分序列。g[i][1]表示的是一个点在左半边,它的后面的点必定只能接值大于它的点,它的前面可以接值小于它的点。f[i][1] 表示的是右半边,它后面只能接值小于它的点,它的前面只能接值大于它的点。
现在考虑如何贪心构造:沿着这个单峰序列从左到右构造,由于每一个最长单峰子序列的点都被按阶段(这里的阶段指的是1 - res)存在桶内。
考虑构造字典序最小的解:枚举阶段 k 从 1 到 res,对于每个阶段,如果当前构造的序列仍然是左半边,那么同时在左半边阶段值为k + 1的桶内选一个序列值和下标都更大的点,在右半边阶段值为res - k + 1 的桶内选一个序列值更小但下标更大的点,然后比较两个选出来的元素的下标,下标较小的点压入答案序列中,若已经在右半边选了一个点,说明已经经过了峰点,接下来只能在右半边阶段值为 k - 1的桶内选一个序列值更小下标更大的点。由于桶内元素具有单调性,这个过程可以直接用二分,直接查找下标较大的即可。构造字典序最大解是一个类似的过程,要下标尽量大可以通过查找值尽量小来实现。
按阶段来构造的答案序列是正确的,因为左半边阶段为 i 的点可以接在阶段 为 i - 1的点的后面,右半边阶段为 i 的点可以接在阶段为 i + 1的点的后面。这是因为求解f[i][0] 数组和求解 g[i][0]数组转移过程,构造答案时可以按转移来构造。
看代码吧
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5 + 10;
#define lson rt << 1,l,mid
#define rson rt << 1 | 1,mid + 1,r
int n,a[maxn];
int mx[maxn << 2],t[maxn],tot,p;
int f[maxn][2],g[maxn][2];
vector<int> st[maxn],ed[maxn];
vector<int> mx_ans,mi_ans;
void init() {
sort(t + 1, t + tot + 1);
p = unique(t + 1,t + tot + 1) - t - 1;
}
int Hash(int x) {
return lower_bound(t + 1,t + p + 1,x) - t;
}
void build(int rt,int l,int r) {
mx[rt] = 0;
if(l == r) return ;
int mid = l + r >> 1;
build(lson);build(rson);
}
void upd(int p,int v,int rt,int l,int r) {
if(l == r) {
mx[rt] = max(mx[rt],v);
return ;
}
int mid = l + r >> 1;
if(p <= mid) upd(p,v,lson);
else upd(p,v,rson);
mx[rt] = max(mx[rt << 1],mx[rt << 1 | 1]);
}
int qry(int L,int R,int rt,int l,int r) {
if(L > R) return 0;
if(L <= l && r <= R) return mx[rt];
int mid = l + r >> 1,ans = 0;
if(L <= mid) ans = max(qry(L,R,lson),ans);
if(mid + 1 <= R) ans = max(ans,qry(L,R,rson));
return ans;
}
bool cmp(int i,int j) { //**
return a[i] > a[j];
}
bool cmp2(int i,int j) {
return a[i] < a[j];
}
int main() {
while(~scanf("%d",&n)) {
p = tot = 0;a[0] = 0;
mx_ans.clear();mi_ans.clear();
for(int i = 1; i <= n; i++) {
scanf("%d",&a[i]);
st[i].clear(),ed[i].clear();
t[++tot] = a[i];
}
init();
build(1,1,p);
for(int i = n; i >= 1; i--) {
int pi = Hash(a[i]);
int res = qry(1,pi - 1,1,1,p);
f[i][1] = f[i][0] = res + 1;
upd(pi,f[i][0],1,1,p);
}
build(1,1,p);
for(int i = n; i >= 1; i--) {
int pi = Hash(a[i]);
f[i][1] = max(f[i][1],qry(pi + 1,p,1,1,p) + 1);
upd(pi,f[i][1],1,1,p);
}
build(1,1,p);
for(int i = 1; i <= n; i++) {
int pi = Hash(a[i]);
int res = qry(1,pi - 1,1,1,p);
g[i][1] = g[i][0] = res + 1;
upd(pi,g[i][0],1,1,p);
}
build(1,1,p);
for(int i = 1; i <= n; i++) {
int pi = Hash(a[i]);
int res = qry(pi + 1,p,1,1,p);
g[i][1] = max(g[i][1],res + 1);
upd(pi,g[i][1],1,1,p);
}
int res = 0;
for(int i = 1; i <= n; i++)
res = max(res,f[i][0] + g[i][0] - 1);
for(int i = 1; i <= n; i++) {
if(g[i][0] + f[i][1] - 1 == res) st[g[i][0]].push_back(i); //左半边
if(g[i][1] + f[i][0] - 1 == res) ed[f[i][0]].push_back(i); //右半边
}
int pos = 0,flag = 0;
for(int i = 1; i <= res; i++) { //贪心构造最小字典序的答案,同一个桶内的答案具有单调性
int l = n + 2,r = n + 2,tmp_pos;
if(!flag) { //还没过峰点
tmp_pos = upper_bound(st[i].begin(),st[i].end(),pos) - st[i].begin();
if(tmp_pos < st[i].size() && a[st[i][tmp_pos]] > a[pos]) l = st[i][tmp_pos];
}
tmp_pos = upper_bound(ed[res - i + 1].begin(),ed[res - i + 1].end(),pos) - ed[res - i + 1].begin();
if(tmp_pos < ed[res - i + 1].size() && a[pos] > a[ed[res - i + 1][tmp_pos]]) r = ed[res - i + 1][tmp_pos];
if(r < l) flag = 1,pos = r;
else pos = l;
mi_ans.push_back(pos);
}
for(int i = 0; i < mi_ans.size(); i++) {
if(i) printf(" ");
printf("%d",mi_ans[i]);
}
puts("");
pos = 0,flag = 0;
for(int i = 1; i <= res; i++) {
int l = 0,r = 0,tmp_pos;
a[0] = 0;
if(!flag) { //还没过峰点
tmp_pos = lower_bound(st[i].begin(),st[i].end(),pos,cmp) - st[i].begin() - 1;
if(tmp_pos >= 0 && a[st[i][tmp_pos]] > a[pos]) l = st[i][tmp_pos];
}
a[0] = 0x3f3f3f3f;
tmp_pos = lower_bound(ed[res - i + 1].begin(),ed[res - i + 1].end(),pos,cmp2) - ed[res - i + 1].begin() - 1;
if(tmp_pos >= 0 && a[pos] > a[ed[res - i + 1][tmp_pos]]) r = ed[res - i + 1][tmp_pos];
if(r > l) flag = 1,pos = r;
else pos = l;
mx_ans.push_back(pos);
}
for(int i = 0; i < mx_ans.size(); i++) {
if(i) printf(" ");
printf("%d",mx_ans[i]);
}
puts("");
}
return 0;
}
(总的来说过程比较繁琐,在第二次看清题意尝试写了一发之后确实想到了这样一种繁琐的方法,构造答案时完全根据g[i][0] 和 f[i][0] 来构造,并且需要判断是否选过峰点,由于思路不是特别清晰,只是大概有这种想法,对桶内答案具有单调性的性质搞不太清楚,最后跟着标程搞了一遍)