愤怒的小鸟

题目

 

 

题解

由于n<=18,可以想到状压dp

然后选择枚举一个抛物线,直接更新

如果用填表法,就会有些难度,而时间复杂度是O(n^{3}*2^{n}

dp[i] = min( dp[cnt] + 1, dp[i] )

其中cnt表示i的点集减去抛物线的点集

代码

//可是用了O2才过
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN = 20;
int n , m , T;
int dp[1<<MAXN];
int sum[MAXN*MAXN][MAXN] , cnt;
double a[MAXN] , b[MAXN];
int main()
{
    scanf( "%d" , &T );
    while( T -- ){
        cnt = 0;
        scanf( "%d%d" , &n , &m );
        memset( sum , 0 , sizeof( sum ) );
        memset( dp , 0x7f, sizeof( dp ) );
        for( int i = 1 ; i <= n ; i ++ ){
            scanf( "%lf%lf" , &a[i] , &b[i] );
            sum[++cnt][1] = i;sum[cnt][0] = 1;
        }
        for( int i = 1 ; i <= n ; i++ ){
            for( int j = i + 1 ; j <= n ; j ++ ){
                if( a[i] * a[i] * a[j] - a[j] * a[j] * a[i] == 0 ) continue;
                double ans1 = ( a[j] * b[i] - a[i] * b[j] ) / ( a[i] * a[i] * a[j] - a[j] * a[j] * a[i] );
                if( ans1 < 0 ){
                    cnt ++;
                    sum[cnt][0] = 2;
                    sum[cnt][1] = i , sum[cnt][2] = j;
                    for( int k = 1 ; k <= n ; k ++ ){
                        if( k == i || k == j ) continue;
                        if( a[j] * a[j] * a[k] - a[k] * a[k] * a[j] == 0 ) continue;
                        double ans2 = ( a[k] * b[j] - a[j] * b[k] ) / ( a[j] * a[j] * a[k] - a[k] * a[k] * a[j] );
                        double t = ans1;
                        if( t < ans2 ) swap( t , ans2 );
                        if( t - ans2 < 1e-6 )
                            sum[cnt][++sum[cnt][0]] = k;
                    }
                }
            }
        }
        dp[0] = 0;
        for( int i = 1 ; i < ( 1 << n ) ; i ++ ){
            for( int j = 1 ; j <= cnt ; j ++ ){
                int p = i;
                for( int k = 1 ; k <= sum[j][0] ; k ++ ){
                    int x = sum[j][k];
                    if( p & ( 1 << ( x -1 ) ) )
                        p = p ^ ( 1 << ( x -1 ) ) ;
                }
                dp[i] = min( dp[p] + 1 , dp[i] );
            }
        }
        printf( "%d\n" , dp[( 1<<n)-1] );
    }
    return 0;
}

这样的效率太慢了,如果用刷表发就可以

时间复杂度O(2^{n}*n^{2})

dp[i|cnt] = min( dp[i] + 1 , dp[i|cnt] )

其中cnt表示的是枚举的是抛物线经过的点集

代码

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN = 20;
int n , m , T;
int dp[1<<MAXN];
int sum[MAXN*MAXN], cnt;
double a[MAXN] , b[MAXN];
int main()
{
    scanf( "%d" , &T );
    while( T -- ){
        cnt = 0;
        scanf( "%d%d" , &n , &m );
        memset( sum , 0 , sizeof( sum ) );
        memset( dp , 0x7f, sizeof( dp ) );
        for( int i = 1 ; i <= n ; i ++ ){
            scanf( "%lf%lf" , &a[i] , &b[i] );
            sum[++cnt] = 1 << (i-1);
        }
        for( int i = 1 ; i <= n ; i++ ){
            for( int j = i + 1 ; j <= n ; j ++ ){
                if( a[i] * a[i] * a[j] - a[j] * a[j] * a[i] == 0 ) continue;
                double ans1 = ( a[j] * b[i] - a[i] * b[j] ) / ( a[i] * a[i] * a[j] - a[j] * a[j] * a[i] );
                if( ans1 < 0 ){
                    cnt ++;
                    sum[cnt]|= ( 1 << ( i - 1 ) ) , sum[cnt]|= ( 1 << ( j - 1 ) );
                    for( int k = 1 ; k <= n ; k ++ ){
                        if( k == i || k == j ) continue;
                        if( a[j] * a[j] * a[k] - a[k] * a[k] * a[j] == 0 ) continue;
                        double ans2 = ( a[k] * b[j] - a[j] * b[k] ) / ( a[j] * a[j] * a[k] - a[k] * a[k] * a[j] );
                        double t = ans1;
                        if( t < ans2 ) swap( t , ans2 );
                        if( t - ans2 < 1e-6 )
                            sum[cnt] |= ( 1 << ( k - 1 ) );
                    }
                }
            }
        }
        dp[0] = 0;
        for( int i = 0 ; i < ( 1 << n ) ; i ++ ){
            for( int j = 1 ; j <= cnt ; j ++ ){
                int o = i | sum[j];
                dp[o] = min( dp[i] + 1 , dp[o] );
            }
        }
        printf( "%d\n" , dp[( 1<<n)-1] );
    }
    return 0;
}

————————————————————————————————————————————————————————

总结

状压刷表填表要灵活应用

(下面还有)

 

 

 

 

 

 

 

 

 

可是还没有完,理论上来说O(2^{n}*n^{2})是不可以过的,有其他方法吗?

还需要对状压dp进行优化

如果找到当前状态点集S,准备去更新其它状态

那么找到x 满足 S & ( 1 << ( x -1) ) == 0的最小正整数

那么S所去更新的状态一定要包括x,否则以后的状态还要回来找经过x的抛物线,就多余了

所以先预处理x,再枚举与x所形成的抛物线的点集P

再用S去更新P+S

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN = 20;
int n , m , T;
int dp[1<<MAXN];
int sum[MAXN][MAXN], cnt;
double a[MAXN] , b[MAXN];
int fu[1<<MAXN];
int main()
{
    for( int i = 0 ; i < ( 1 << 18 ) ; i ++ ){
        for( int j = 1 ; j <= 18 ; j ++ )
            if( ( i & ( 1 << (j-1) ) )== 0 ){
                fu[i] = j;
                break;
            }
        if( !fu[i] )
            fu[i] = 18;
    }
    scanf( "%d" , &T );
    while( T -- ){
        scanf( "%d%d" , &n , &m );
        memset( sum , 0 , sizeof( sum ) );
        memset( dp , 0x3f, sizeof( dp ) );
        for( int i = 1 ; i <= n ; i ++ ){
            scanf( "%lf%lf" , &a[i] , &b[i] );
        }
        for( int i = 1 ; i <= n ; i++ ){
            for( int j = 1 ; j <= n ; j ++ ){
                if( i ==j  ) continue;
                if( a[i] * a[i] * a[j] - a[j] * a[j] * a[i] == 0 ) continue;
                double ans1 = ( a[j] * b[i] - a[i] * b[j] ) / ( a[i] * a[i] * a[j] - a[j] * a[j] * a[i] );
                if( ans1 < 0 ){
                    sum[i][j]|= ( 1 << ( i - 1 ) ) , sum[i][j]|= ( 1 << ( j - 1 ) );
                    for( int k = 1 ; k <= n ; k ++ ){
                        if( k == i || k == j ) continue;
                        if( a[j] * a[j] * a[k] - a[k] * a[k] * a[j] == 0 ) continue;
                        double ans2 = ( a[k] * b[j] - a[j] * b[k] ) / ( a[j] * a[j] * a[k] - a[k] * a[k] * a[j] );
                        double t = ans1;
                        if( t < ans2 ) swap( t , ans2 );
                        if( t - ans2 < 1e-8 )
                            sum[i][j] |= ( 1 << ( k - 1 ) );
                    }
                }
            }
        }
        dp[0] = 0;
        for( int i = 0 ; i < ( 1 << n ) ; i ++ ){
            int j = fu[i];
            dp[i|( 1 <<(j-1))] = min( dp[i|( 1 <<(j-1) )] , dp[i] + 1 );
            for( int k = 1 ; k <= n ; k ++ ){
                if( sum[j][k] )
                    dp[i|sum[j][k]] = min( dp[i] + 1 , dp[i|sum[j][k]] );
            }
        }
        printf( "%d\n" , dp[( 1<<n)-1] );
    }
    return 0;
}

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页