列表

详情


NC222381. 音游家的谱面(Hardversion)

描述

(本题和Easy version的区别仅在数据范围不同

牛牛最近在玩一款叫做牛特斯的下落式音游,下落式音游是一种音乐节拍类游戏,在屏幕上平行分布若干条轨道,音符在这些轨道上移动。
当音符与屏幕底部的判定线重合时点击音符即可命中。对于有条轨道的音游被称之为是一个 Key音游,例如常见的4 Key音游,5 Key音游,8 Key音游。

在一轮游戏中共有个音符,对于每一个音符,都有两个属性为正整数,表示音符下落时所属的轨道为是一个实数,表示音符到达判定线的时间为毫秒,由于是一个实数,同时出现的音符被理解成间隔非常小的时间单位,所以允许音符同时出现。


游玩这类音游时,一般人会使用两只手,或者两只拇指,并且游戏开始时左手放在1号轨道,右手放在第号轨道。在第毫秒中(第毫秒这段时间区间,左闭右开),如果两只手分别放在这两个位置上,那么理论上就可以命中在第i毫秒轨道到达判定线的所有音符。(当然,如果你技术够好的话)

在每一毫秒末,两只手可以独立的各自进行移动,但是只能移动到它们相邻的轨道上。具体来讲,假设左右手在第毫秒中位于这两个位置上,记为。那么在第毫秒末,共有9种不同的移动方案,即

在音游中,有一些谱面由于过度反人类,不适合人类游玩而被称为是“粪谱”。
具体来讲,如果假设一开始左手在第号轨道,右手在第号轨道,双手各自独立的按照上述规则,每毫秒末移动一个单位。不存在任何一种方案可以在理论上全部命中的个音符,就称这个谱面是一个“粪谱”。


我们称最后一个音符,即第个音符到达判定线的时间为整个谱面的时长。

现在给你这个音符按照时间顺序的轨道坐标,请你设计一个谱面时长最小的非“粪谱”谱面。
也就是说你需要给出这个音符到达判定线的时间,并且要求可以为实数,但是为了避免一些不必要的精度问题,你只需要输出即可。


当然,符合条件的谱面不唯一,你只用输出任意一种符合题目要求的即可。

输入描述

第一行输入两个正整数,表示音游的轨道数目和音符数。

接下来一行个正整数,表示这个音符按照时间顺序出现的轨道编号。

输出描述

输出一行,表示每个音符到达判定线的时间向下取整后的结果。

示例1

输入:

10 10
1 2 3 4 5 6 7 8 9 10

输出:

0 1 2 3 4 4 5 6 7 8

说明:

10 Key音游,共有10个下落音符,并且按照时间顺序分别出现在1~10号轨道。
其中一种谱面时长最小的非“粪谱”谱面可以是:

time_1=0.1
time_2=1.1
time_3=2.1
time_4=3.1
time_5=4.5
time_6=4.5
time_7=5.1
time_8=6.1
time_9=7.1
time_{10}=8.0

在第{0}毫秒即{[0,1)}这段时间内,左手在{1},右手在{10},左手敲击第{1}个音符,然后在第{0}毫秒末,左手向右移动一个轨道,同时右手向左移动一个轨道。
在第{1}毫秒即{[1,2)}这段时间内,左手在{2},右手在{9},左手敲击第{2}个音符,然后在第{1}毫秒末,左手向右移动一个轨道,同时右手向左移动一个轨道。
在第{2}毫秒即{[2,3)}这段时间内,左手在{3},右手在{8},左手敲击第{3}个音符,然后在第{2}毫秒末,左手向右移动一个轨道,同时右手向左移动一个轨道。
在第{3}毫秒即{[3,4)}这段时间内,左手在{4},右手在{7},左手敲击第{4}个音符,然后在第{3}毫秒末,左手向右移动一个轨道,同时右手向左移动一个轨道。
在第{4}毫秒即{[4,5)}这段时间内,左手在{5},右手在{6},左右手同时敲击第{5}{6}个音符,然后在第{4}毫秒末,右手向右移动一个轨道,左手原地摸鱼。
在第{5}毫秒即{[5,6)}这段时间内,左手在{5},右手在{7},右手敲击第{7}个音符,然后在第{5}毫秒末,右手向右移动一个轨道,左手继续摸鱼。
在第{6}毫秒即{[6,7)}这段时间内,左手在{5},右手在{8},右手敲击第{8}个音符,然后在第{6}毫秒末,右手向右移动一个轨道,左手继续摸鱼。
在第{7}毫秒即{[7,8)}这段时间内,左手在{5},右手在{9},右手敲击第{9}个音符,然后在第{7}毫秒末,右手向右移动一个轨道,左手继续摸鱼。
在第{8}毫秒即{[8,9)}这段时间内,左手在{5},右手在{10},右手敲击第{10}个音符,游戏结束,谱面总用时共{8}毫秒。

示例2

输入:

1000 6
1 1000 1 1000 1 1000

输出:

0 0 0 0 0 0

说明:

由于{time_i}是一个实数,同时出现的音符被理解成间隔非常小的时间单位,所以允许音符同时出现。
一开始左手在{1},右手在{1000}。然后因为{time_i}都很小,\left \lfloor time_i \right \rfloor=0。你不用很关心它们的值具体是多少,只用知道{time_i}是个很小的数然后输出{0}即可。

示例3

输入:

4 10
1 4 2 3 4 1 3 2 1 4

输出:

0 0 1 1 2 2 3 3 4 4

说明:

注意题目要求,要保证是非“粪谱”并且音符到达判定线的时间满足time_1 \leq time_2 \leq ... \leq time_M

示例4

输入:

100 2
98 10

输出:

9 9

说明:

答案不唯一
2 9
3 9
4 9
...
9 9
都是符合题目要求的解

原站题解

上次编辑到这里,代码来自缓存 点击恢复默认模板

C++ 解法, 执行用时: 371ms, 内存消耗: 197048K, 提交时间: 2021-06-27 23:49:10

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int maxn = 5e3+10;
int n,m,f[maxn][maxn],a[maxn],pre[maxn][maxn],l[maxn],r[maxn];
typedef pair<int,int>p;
p fl[maxn],fr[maxn],q[maxn];
void dfs(int fir,int sec)
{
	if( fir==0 )	return;
	dfs( fir-1,pre[fir][sec] );
	cout << f[fir][sec] << " ";
}
void solve()
{
	int ans = inf, chu = 0;
	for(int i=1;i<=n;i++)
	{
		if( f[m][i]<ans )	ans = f[m][i],chu = i;
		if( f[m][i]<ans )	ans = f[m][i],chu = i;
	}
	dfs( m,chu );
}
int main()
{
	cin >> n >> m;
	for(int i=1;i<=m;i++)	cin >> a[i];
	memset( f,0x3f3f3f3f,sizeof f );
	f[0][n] = 0, a[0] = 1;
	for(int i=1;i<=m;i++)
	{
		int lim = abs( a[i]-a[i-1] );
		for(int j=1;j<=n;j++)	l[j] = max(1,j-lim), r[j] = min( n,j+lim );
		int head = 1, tail = 0, z = 1;
		for(int j=1;j<=n;j++)//上一次一只手为j,另一只手为a[i-1]
		{
			//按音符的手去按下一个音符,这是一个定值,只需要求出左右区间长度为lim的中f[i-1][j]的最小值即可 
			while( z<=r[j] )
			{
				while( tail>=head && f[i-1][z]+lim<=q[tail].fi )	tail--;
				q[++tail] = p( f[i-1][z]+lim,z );
				z++; 	
			}
			while( q[head].se<l[j] )	head++;
			f[i][j] = q[head].fi, pre[i][j] = q[head].se;
		}

		for(int j=0;j<=n+1;j++)	fl[j] = fr[j] = p(inf,inf);
		for(int j=1;j<=n;j++)//可以更新的窗口[]递减
		{
			lim = abs( j-a[i] );//递减 
			int le = max( 1,a[i-1]-lim ), re = min( n,a[i-1]+lim );//可更新窗口
			fl[le] = min( fl[le],p( f[i-1][j]+lim,j ) );
			fr[re] = min( fr[re],p( f[i-1][j]+lim,j ) );
		}
		for(int j=1;j<=a[i-1];j++)	fl[j] = min( fl[j],fl[j-1] );//往右边max
		for(int j=n;j>=a[i-1];j--)	fr[j] = min( fr[j],fr[j+1] );//往左边取max 
		for(int j=1;j<=a[i-1];j++)
			if( f[i][j]>fl[j].fi )	f[i][j] = fl[j].fi, pre[i][j] = fl[j].se;	
		for(int j=a[i-1];j<=n;j++)
			if( f[i][j]>fr[j].fi )	f[i][j] = fr[j].fi, pre[i][j] = fr[j].se;
	}
	solve(); 
}

上一题