题解 P2831 【愤怒的小鸟】
【题意简述】
平面的第一象限内有
【题目简析】
此处摘录自https://www.luogu.org/blog/1000suns/solution-p2831
设计
dp[S|line[i][j]]=\min(dp[S]+1) dp[S|(1<<(i-1)]=\min(dp[S]+1)
其中
这就是网上大多流传的
优化1:
发现当
证明:
若这条线只经过至多三个点,因为其中一个点已被打到,所以可以通过另外两个点的状态转移。如果
优化2:
若令
为什么?这个是对的吗?不经过
你想一想,先打
如果这一次转移不打
因为经过
变成了
只要预处理一下
【代码简释】
#include <bits/stdc++.h>
using namespace std ;
const double eps = 1e-10 ;
int T , n , m ;
double x[ 20 ] , y[ 20 ] ;
int lines[ 20 ][ 20 ] ;
int lowunbit[ 1 << 20 ] , dp[ 1 << 20 ] ;
inline void equ ( double &a , double &b , double a1 , double b1 , double c1 , double a2 , double b2 ,double c2 ) { // 解二次函数,求出二次函数的函数关系式(将2个点的坐标带入,即可求得a和b
b = ( a1 * c2 - a2 * c1 ) / ( a1 * b2 - a2 * b1 ) ;
a = ( c1 - b1 * b ) / a1 ;
}
signed main () {
ios :: sync_with_stdio ( false ) ;
cin >> T ;
for ( int i = 0 ; i < ( 1 << 18 ) ; i ++ ) {
int j = 1 ;
for ( ; j <= 18 && i & ( 1 << ( j - 1 ) ) ; j ++ ) ;
lowunbit[ i ] = j ;
} // 预处理出x
while ( T -- ) {
memset ( lines , 0 , sizeof ( lines ) ) ;
memset ( dp , 0x3f , sizeof ( dp ) ) ;
dp[ 0 ] = 0 ;
cin >> n >> m ;
for ( int i = 1 ; i <= n ; i ++ ) {
cin >> x[ i ] >> y[ i ] ;
}
for ( int i = 1 ; i <= n ; i ++ ) {
for ( int j = 1 ; j <= n ; j ++ ) {
if ( fabs ( x[ i ] - x[ j ] ) < eps ) continue ;
double a , b ;
equ ( a , b , x[ i ] * x[ i ] , x[ i ] , y[ i ] , x[ j ] * x[ j ] , x[ j ] , y[ j ] ) ;
if ( a > -eps ) continue ;
for ( int k = 1 ; k <= n ; k ++ ) {
if ( fabs ( a * x[ k ] * x[ k ] + b * x[ k ] - y[ k ] ) < eps ) lines[ i ][ j ] |= ( 1 << ( k - 1 ) ) ; // 预处理出经过i,j的抛物线经过点的集合状态
}
}
}
/*for ( int i = 1 ; i <= n ; i ++ ) {
for ( int j = 1 ; j <= n ; j ++ ) {
cout << lines[ i ][ j ] << " " ;
}
cout << endl ;
}*/
for ( int s = 0 ; s < ( 1 << n ) ; s ++ ) { // 状压DP
int j = lowunbit[ s ] ;
//cout << ( 1 << ( j - 1 ) ) << endl ;
dp[ s | ( 1 << ( j - 1 ) ) ] = min ( dp[ s | ( 1 << ( j - 1 ) ) ] , dp[ s ] + 1 ) ;
//cout << dp[ s | ( 1 << ( j - 1 ) ) ] << endl ;
for ( int k = 1 ; k <= n ; k ++ ) {
//cout << k << endl ;
dp[ s | lines[ j ][ k ] ] = min ( dp[ s | lines[ j ][ k ] ] ,dp[ s ] + 1 ) ;
}
}
printf ( "%d\n" , dp[ ( 1 << n ) - 1 ] ) ;
}
return 0 ;
}