[CDOJ887]轻音乐同好会(跳石头)

此题已经无法在UESTC上上传,网上只有玄学题解

题目

题目描述

雪菜为了能让冬马参加轻音乐同好会,瞒着春希,和冬马见面。

为了增进感情,雪菜拉着还没缓过神来的冬马进了游戏厅……

游戏要求两名玩家在排成一排的石头上跳跃前进,每个石头有一个高度,玩家只能向右跳,并且不能跳向比自己所在位置的石头矮的石头。一个石头在一个玩家跳离后就会消失,并且两个玩家不能同时站在同一个石头上。游戏分数为两个玩家站过的石头的总数。游戏起始,两名玩家都可以任选一个石头作为开始位置(当然不能相同)。
由于冬马是挂科专家,雪菜又只有英语好,所以她们两人想请你帮助他们,怎么才能让分数最高。

Input

第一行一个整数 \(n\),表示有 \(n\)个石头。\((2 \le n \le 1000)\)
第二行 \(n\) 个整数,表示从左到右第 \(i\) 个石头的高度 \(H_i\)\((H_i \le109)\)

Output

一个整数,表示最高能得到的分数。

解析

题目大意

求两个元素不重复最长不降子序列的长度

分析

显然如果一个人已经确定了跳的石头,第二个人一定跳的是最长不降子序列。以为两个人地位对等,因此可以确定两个人跳的都是最长不降子序列。
但如果直接做两遍最长不降子序列,不能保证你当前选取的最长不降子序列不会影响第二次最长不降子序列的大小,所以显然是错误的
考虑遍历所有第一个人会取的最长不降子序列,打完标记之后,再跑第二个人最长不降子序列

方法一
先跑一遍求LIS,然后记录下这个值,然后维护多个队列记录所有的长度为这个的情况,然后枚举再做一遍。
复杂度\(nlog^2(n)\)

方法二
使用用单调队列,动态维护所有情况,具体方法:
对于第 \(i\) 个元素, 二分在队列中找到一个刚好比它大的位置记为 \(j\),进行替换,显然,\(h[1...j-1]\) 能与这第 \(i\) 个元素构成长度为 \(j\) 的子序列,并且,这种长度为 \(j\) 的子序列优于原始的子序列。显然,当我们的队列长度更新的时候,我们能选取的最长序列就是这个长度。

对于被替换的元素, 我们将其下放一层,进入下一层单调队列进行同等操作。由于性质的类似性,我们能证明这种操作是正确的。

于是,我们将这个问题推广到了 \(k\) 维:求 \(k\) 个元素不重复的最长不降子序列的长度;

复杂度\(nlogn\)

代码

注意\(t[i]\)表示第 \(i\) 层的队列长度; \(q[i]\)表示第 \(i\)

#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
#define N 5005
int n, i, x, t[N]; 
int q[N][N];

void up(int p, int x) {
	if (x >= q[p][t[p]]) { q[p][++t[p]] = x; return; }
	int l = 1, r = t[p], mid, u;
	while (l <= r) {
		if (q[p][mid = (l + r) >> 1] > x) {
			u = mid; r = mid - 1;
		} else l = mid + 1;
	}
	up(p + 1, q[p][u]), q[p][u] = x;
}

int main() {
	scanf("%d", &n);
	for (i = 1; i <= n; ++ i) scanf("%d",&x), up(1,x);
	for (int i = 1; ; ++ i) {
		t[i] += t[i - 1];
		if (t[i] == n) break;
	}
	printf("%d", t[2]);
	return 0;
}

posted @ 2018-09-29 18:39  AlessandroChen  阅读(269)  评论(2编辑  收藏  举报