二分
选数问题:选出集合中第\(i\)小的数
如果排序的话,复杂度是\(nlogn\)的,是因为他对一些没有用的东西也进行了操作,所以我们尽量少进行没有用的操作,有一种\(O(n)\)的算法
内容见\(link\)
复杂度证明:每次都选择一个数后,我们只用向另一半递归,所以计算次数为
\(n+n/2+n/4+n/8+....+1\),易知这是一个收敛的等比数列,和不会大于\(2n\),故复杂度是\(O(n)\)
所以说复杂度不能想当然\(logn\),还是要自己推,一般如果\(check\)函数的计算范围随着端点的缩小而缩小,复杂度就是\(O(n)\)的,如果\(check\)复杂度一直是\(O(n)\),那么就是\(nlogn\)
code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <ctime>
#define int long
#define R register int
#define printf tz1=printf
using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned G ;
const int N = 5e6 + 10 ;
inline int read(){
int w = 0 ; bool fg = 0 ; char ch = getchar() ;
while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch - '0' ) , ch = getchar() ;
return fg ? -w : w ;
}
int tz1 , n , m , a [N] ;
void sc(){
n = read() , m = read() ; m ++ ;
for( R i = 1 ; i <= n ; i ++ ) a [i] = read() ;
}
inline void debug( int l , int r ){
for( R i = l ; i <= r ; i ++ ) printf( "%ld " , a [i] ) ; puts( "" ) ;
}
inline void choose( int l , int r , int x ){
// printf( "L%ld R%ld X%ld\n" , l , r , x ) ;
int mid = ( l + r ) >> 1 ; //懒得写随机,所以取的中点
swap( a [mid] , a [l] ) ;
int cnt = l ; //debug( l , r ) ;
for( R i = l + 1 ; i <= r ; i ++ )
if( a [i] < a [l] ) swap( a [++ cnt] , a [i] ) ;
swap( a [cnt] , a [l] ) ;
if( cnt - l == x - 1 ) { printf( "%ld\n" , a [cnt] ) ; return ; }
if( cnt - l < x ) choose( cnt + 1 , r , x - cnt + l - 1 ) ;
else choose( l , cnt , x ) ;
}
void work(){
choose( 1 , n , m ) ;
}
signed main(){
sc() ;
work() ;
return 0 ;
}
一种二分答案保证可以去到所有点的写法(\(orz\ zero4338\)赐教)
int lside = 0 , rside = balabala , ans ;
while( lside <= rside ){
mid = ( lside + rside ) >> 1 ;
if( check() ) ans = mid , rside = mid - 1 ; //这里要看check如何定义缩小左右边界
else lside = mid + 1 ;
} printf( "%ld\n" , ans ) ;
其实很多题都可以二分答案,所以见到题看有没有单调性就可以直接想如何写\(check\)了
这里放几个二分答案的链接
\(link1\)
\(link2\)
\(link3\)
\(link4\)
\(link5\)
$The \ light \ has \ betrayed \ me$

浙公网安备 33010602011771号