题意:给N个数,多次询问区间中min(abs(a[i]-a[j]))
题解:假老师的题解好神啊QAQ
莫队算法,假设当前处理l[i]在某一块[L,R]的询问
把询问按右端点排序,从右到左删除,从左到右加入一次记录f[i]表示从R加到i时的贡献
从右到左再删除,一边删除一边处理询问,暴力删除后加入即可
用了链表的一个trick写法
#include <bits/stdc++.h>
using namespace std;
typedef pair< int, int > pa;
const int MAXN = 100010;
const int MAXM = 320;
const int INF = 1e9 + 7;
inline int read()
{
int sc = 0; char ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
while( ch >= '0' && ch <= '9' ) sc = sc * 10 + ch - '0', ch = getchar();
return sc;
}
struct Query
{
int l, r, id;
bool operator < ( const Query &b ) const { return r < b.r; }
};
int n, m, a[MAXN], pre[MAXN], nxt[MAXN], block, sorted[MAXN], dp[MAXM][MAXN], ans[MAXN], f[MAXN];
vector < Query > v[MAXM];
pa p[MAXN];
inline void clear()
{
for( int i = 1 ; i <= n ; i++ ) pre[ i ] = i - 1, nxt[ i ] = i + 1;
pre[ n + 1 ] = n; nxt[ 0 ] = 1;
}
inline void del(int x)
{
pre[ nxt[ x ] ] = pre[ x ];
nxt[ pre[ x ] ] = nxt[ x ];
}
inline int ins(int x)
{
int ret = INF, t;
t = nxt[ x ];
if( t <= n ) ret = min( ret, sorted[ t ] - sorted[ x ] );
t = pre[ x ];
if( t ) ret = min( ret, sorted[ x ] - sorted[ t ] );
pre[ nxt[ x ] ] = x; nxt[ pre[ x ] ] = x;
return ret;
}
int main()
{
n = read(); block = sqrt( n );
for( int i = 1 ; i <= n ; i++ ) a[ i ] = read(), p[ i ] = make_pair( a[ i ], i );
sort( p + 1, p + n + 1 );
for( int i = 1 ; i <= n ; i++ ) dp[ 1 ][ i ] = INF;
for( int i = 2 ; i < MAXM ; i++ )
for( int j = 1 ; j + i - 1 <= n ; j++ )
dp[ i ][ j ] = min( min( dp[ i - 1 ][ j ], dp[ i - 1 ][ j + 1 ] ), abs( a[ j ] - a[ j + i - 1 ] ) );
for( int i = 1 ; i <= n ; i++ ) a[ i ] = lower_bound( p + 1, p + n + 1, make_pair( a[ i ], i ) ) - p;
for( int i = 1 ; i <= n ; i++ ) sorted[ i ] = p[ i ].first;
int m = read();
for( int i = 1 ; i <= m ; i++ )
{
Query q;
q.l = read(); q.r = read(); q.id = i;
if( q.r - q.l + 1 < MAXM ) ans[ i ] = dp[ q.r - q.l + 1 ][ q.l ];
else v[ q.l / block ].push_back( q );
}
for( int i = 0 ; i < MAXM ; i++ )
{
if( v[ i ].empty() ) continue;
clear();
int L = i * block, R = L + block - 1;
for( int j = 1 ; j < R ; j++ ) del( a[ j ] );
for( int j = n ; j > R ; j-- ) del( a[ j ] );
f[ R ] = INF;
for( int j = R + 1 ; j <= n ; j++ ) f[ j ] = min( f[ j - 1 ], ins( a[ j ] ) );
for( int j = R - 1 ; j >= L ; j-- ) ins( a[ j ] );
sort( v[ i ].begin(), v[ i ].end() );
for( int j = v[ i ].size() - 1, k = n ; j >= 0 ; j-- )
{
Query q = v[ i ][ j ];
while( k > q.r ) del( a[ k-- ] );
int cur = f[ k ];
for( int c = L ; c < R ; c++ ) del( a[ c ] );
for( int c = R - 1 ; c >= q.l ; c-- ) cur = min( cur, ins( a[ c ] ) );
for( int c = q.l - 1 ; c >= L ; c-- ) ins( a[ c ] );
ans[ q.id ] = cur;
}
}
for( int i = 1 ; i <= m ; i++ ) printf( "%d\n", ans[ i ] );
}
另一种线段树做法
我们将询问按r排序,每次动态加入一个点
在线段树每个节点维护这一段的有序序列,和这一段的答案
考虑加入a[r]时,会修改前面所有点作为左端点的答案
但是,我们发现,每个点作为左端点的答案,从左到右是递增的
否则,右边有一个更优的答案,查询的时候包含了右边,那么左边的答案事实上是没有用的
所以记录当前的最小答案d
先更新右边,再更新左边
如果当前加入的数x在这一段的前驱和后继与x的差都大于等于d,那么答案不优,结束更新
否则递归更新
这样复杂度应该是有保证的
考虑最坏的情况是更新到了最左边的端点
那么序列应该是a[1]=1,a[2]=n, a[i]=(a[i-1]+a[1])/2-1才会达到最坏情况
每次把值域减少一半,最多只有log次更新
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
const int INF = 1e9 + 7;
inline int read()
{
int sc = 0; char ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
while( ch >= '0' && ch <= '9' ) sc = sc * 10 + ch - '0', ch = getchar();
return sc;
}
int a[MAXN], n, m, ans[MAXN << 2];
struct node
{
vector < int > v;
int ans;
}e[MAXN << 2];
struct Query
{
int l, r, id;
bool operator < ( const Query &b ) const { return r < b.r; }
}q[MAXN << 2];
inline void merge(vector < int > &a, vector < int > b, vector < int > c)
{
int szb = b.size(), szc = c.size();
int i = 0, j = 0;
for( ; i < szb ; i++ )
{
while( j < szc && c[ j ] < b[ i ] ) a.push_back( c[ j++ ] );
a.push_back( b[ i ] );
}
while( j < szc ) a.push_back( c[ j++ ] );
return ;
}
inline void build(int x, int l, int r)
{
e[ x ].ans = INF;
if( l == r )
{
e[ x ].v.push_back( a[ l ] );
return ;
}
int mid = l + r >> 1;
build( x << 1, l, mid );
build( x << 1 | 1, mid + 1, r );
merge( e[ x ].v, e[ x << 1 ].v, e[ x << 1 | 1 ].v );
for( int i = 1 ; i <= r - l ; i++ ) e[ x ].ans = min( e[ x ].ans, e[ x ].v[ i ] - e[ x ].v[ i - 1 ] );
return ;
}
inline int query(int x, int l, int r, int ql, int qr)
{
if( l == ql && r == qr ) return e[ x ].ans;
int mid = l + r >> 1;
if( qr <= mid ) return query( x << 1, l, mid, ql, qr );
if( ql > mid ) return query( x << 1 | 1, mid + 1, r, ql, qr );
return min( query( x << 1, l, mid, ql, mid ), query( x << 1 | 1, mid + 1, r, mid + 1, qr) );
}
inline void modify(int x, int l, int r, int qr, int v, int &d)
{
// printf( "%d %d %d %d %d %d\n", x, l, r, qr, v, d );
if( l == r )
{
e[ x ].ans = min( e[ x ].ans, abs( v - e[ x ].v[ 0 ] ) );
d = min( d, e[ x ].ans );
return ;
}
int mid = l + r >> 1;
vector < int > :: iterator it = lower_bound( e[ x ].v.begin(), e[ x ].v.end(), v );
if( ( it == e[ x ].v.end() || *it >= v + d ) && ( it == e[ x ].v.begin() || *( it - 1 ) <= v - d ) )
{
d = min( d, query( x, l, r, l, qr ) );
return ;
}
if( qr > mid )
modify( x << 1 | 1, mid + 1, r, qr, v, d ),
modify( x << 1, l, mid, mid, v, d );
else
modify( x << 1, l, mid, qr, v, d );
e[ x ].ans = min( e[ x ].ans, min( e[ x << 1 ].ans, e[ x << 1 | 1 ].ans ) );
}
int main()
{
n = read();
for( int i = 1 ; i <= n ; i++ ) a[ i ] = read();
build( 1, 1, n );
m = read();
for( int i = 1 ; i <= m ; i++ ) q[ i ].l = read(), q[ i ].r = read(), q[ i ].id = i;
sort( q + 1, q + m + 1 );
int r = 1, d;
for( int i = 1 ; i <= m ; i++ )
{
while( r < q[ i ].r ) d = INF, modify( 1, 1, n, r, a[ r + 1 ], d ), r++;
ans[ q[ i ].id ] = query( 1, 1, n, q[ i ].l, q[ i ].r );
}
for( int i = 1 ; i <= m ; i++ ) printf( "%d\n", ans[ i ] );
return 0;
}