「CEOI2010」MP3 Player
题目
点这里看题目。
分析
首先需要弄清楚如何枚举 \(t\)。由于无论按键是否有效,播放器都会被重置状态。因此,某个按键是否有效仅仅取决于上一个按键与此的时间差和 \(t\) 的关系。那么我们就可以很好地用相邻差来划分 \(t\) 的阶段——有效的 \(t\) 的阶段只有 \(O(n)\) 个。枚举 \(t\) 并模拟。当我们求出 \(t_{\max}\) 之后,剩下的 \(V_2\) 是关于 \(V_1\) 的单调不降的函数,直接二分就好了,复杂度为 \(O(n^2)\)。
考虑加速枚举 \(t\) 的过程。实际上在某个确定的阶段中,我们仅仅想要知道是否存在 \(V_1\),使得最终计算出来的结果为 \(V_2\)——或者,我们可以构造一个函数 \(f_t(V_1)\),而我们只需要考察 \(V_2\) 是否在 \(V_1\) 之中。
没有操作的情况是 \(f(V_1)=V_1\),而有一个 +
的操作是 \(f(V_1)=\min\{V_{\max},V_1+1\}\),有一个 -
的操作是 \(f(V_1)=\max\{0,V_1-1\}\)。多次操作则是这样的基本函数的复合,而更一般的情况是,我们发现 \(f_t(V_1)\) 总可以被描述为 \(\min\{\max\{x+c,a\},b\}\),它的复合也可以比较容易地 \(O(1)\) 计算。
因此,我们的问题变成了——动态维护 \(O(n)\) 个函数的复合结果,并且需要支持将某一个函数重置为 \(f(V_1)=V_1\)。由于结合律,这是一个比较简单的、可以使用线段树维护的问题。总复杂度即为 \(O(n\log n)\)。
小结:
能做题的前提是能把题读懂。- 能够得到这样一个思路,主要是因为我们可以发现 \(f(V_1)\) 应当是连续的整数区间,进一步地发现 \(f(V_1)\) 的简单形式。
- 熟悉函数复合的形式,熟悉 \(\min,\max\) 的运算法则。
代码
#include <cstdio>
#include <utility>
#include <algorithm>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int MAXN = 2e5 + 5;
template<typename _T>
void read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
void write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
_T MIN( const _T a, const _T b ) {
return a < b ? a : b;
}
template<typename _T>
_T MAX( const _T a, const _T b ) {
return a > b ? a : b;
}
int Vmax;
struct Func {
int a, b, c;
Func(): a( 0 ), b( Vmax ), c( 0 ) {}
Func( int A, int B, int C ): a( A ), b( B ), c( C ) {}
inline int operator () ( const int &x ) const { return MIN( MAX( x + c, a ), b ); }
inline Func operator [] ( const Func &g ) const {
return Func( ( * this ) ( g.a ), ( * this ) ( g.b ), c + g.c );
}
};
typedef std :: pair<int, int> Event;
Event eve[MAXN];
int tot = 0;
Func tre[MAXN << 2];
int tim[MAXN];
bool swt[MAXN];
int N, V2;
void Build( const int x, const int l, const int r ) {
if( l == r ) {
if( swt[l] )
tre[x] = Func( 0, Vmax, -1 );
else tre[x] = Func( 0, Vmax, 1 );
return ;
}
int mid = ( l + r ) >> 1;
Build( x << 1, l, mid );
Build( x << 1 | 1, mid + 1, r );
tre[x] = tre[x << 1 | 1][tre[x << 1]];
}
void Reset( const int x, const int l, const int r, const int p ) {
if( l == r ) { tre[x] = Func(); return ; }
int mid = ( l + r ) >> 1;
if( p <= mid ) Reset( x << 1, l, mid, p );
else Reset( x << 1 | 1, mid + 1, r, p );
tre[x] = tre[x << 1 | 1][tre[x << 1]];
}
int Solve() {
int l = 0, r = Vmax, mid;
while( l < r ) {
mid = ( l + r + 1 ) >> 1;
if( tre[1]( mid ) <= V2 ) l = mid;
else r = mid - 1;
}
return l;
}
signed main() {
read( N ), read( Vmax ), read( V2 );
rep( i, 1, N ) {
char op[5];
scanf( "%s", op ), read( tim[i] );
swt[i] = op[0] == '-';
}
Build( 1, 2, N );
if( tre[1]( 0 ) <= V2 && V2 <= tre[1]( Vmax ) )
return puts( "infinity" ), 0;
rep( i, 1, N - 1 ) eve[++ tot] = Event( tim[i + 1] - tim[i], i + 1 );
std :: sort( eve + 1, eve + 1 + tot,
[] ( const Event &a, const Event &b ) -> bool {
return a.first > b.first;
} );
for( int i = 1, j ; i <= tot ; i = j ) {
for( j = i ; j <= tot && eve[j].first == eve[i].first ; )
Reset( 1, 2, N, eve[j ++].second );
if( tre[1]( 0 ) <= V2 && V2 <= tre[1]( Vmax ) ) {
write( eve[i].first - 1 ), putchar( ' ' );
write( Solve() ), putchar( '\n' );
break;
}
}
return 0;
}