P2831 愤怒的小鸟 [状压dp/模拟退火]

愤 怒 的 小 鸟 愤怒的小鸟


D e s c r i p t i o n \mathcal{Description} Description

在第一象限给出 N N N个点, 要求使用最少的 y = a x 2 + b x y=ax^2+bx y=ax2+bx 抛物线覆盖所有点., ( a < 0 a<0 a<0)

N < = 18 N<=18 N<=18


S o l u t i o n \mathcal{Solution} Solution

最 初 想 法 最初想法
枚举第一个点 i i i, 再枚举 x j > = x i x_j>=x_i xj>=xi 的点 j j j,
i , j i,j i,j 可以确定一条抛物线, 计算这条抛物线经过的点数,
取经过点数最多的 i , j i,j i,j 点对, 画出这条抛物线, 答案 +1,
依次 贪心 下去, 得到答案.
提交 => 45 p t s 45pts 45pts

数据范围 " N < = 18 " "N<=18" "N<=18", 考虑 %你退火,
最优方案一定可以排成一个排列, 可以分为连续的几部分, 每部分都可以被同一抛物线覆盖.
于是问题就转化为 " 寻 找 最 优 排 列 " "寻找最优排列" "",
%你退火 就可以了, 100 p t s \color{red}{100pts} 100pts.


正 解 部 分 正解部分
其实这道题的正解是 状 压 d p 状压dp dp,
F [ i ] F[i] F[i] 表示 i i i 状态时的最大值, p [ j ] p[j] p[j] 表示所有可能的抛物线,
注意在预处理抛物线时, 避免同一抛物线重复计入抛物线数组, 相当于一个小优化.
枚举 j j j, 进行状态转移, F [ i ∣ p [ j ] ] = m i n ( F [ i ] + 1 ) F[i|p[j]] = min(F[i]+1) F[ip[j]]=min(F[i]+1)
时间复杂度小于 O ( T ∗ N 2 ∗ 2 N ) O(T*N^2*2^N) O(TN22N),

但是!
O ( T ∗ N 2 ∗ 2 N ) O(T*N^2*2^N) O(TN22N) 并不是最优算法, 最优算法是 : 模拟退火
待填坑.


模 拟 退 火   208 m s 模拟退火\ 208ms 退 208ms, 状 压 d p   2608 m s 状压dp\ 2608ms dp 2608ms (滑稽


实 现 部 分 实现部分
没什么好说的.


C o d e \mathcal{Code} Code

贪 心 贪心 代码 ↓   45 p t s ↓\ 45pts  45pts

#include
#define reg register

int N;
int M;

bool Used[25];

struct Bird{ double x, y; } A[25];
bool cmp(Bird a, Bird b){ return a.x < b.x; }

void Calc(int i, int j, double &a, double &b){ 
        double k1 = A[i].x*A[i].x, k2 = A[i].x; 
        double k3 = A[j].x*A[j].x, k4 = A[j].x; 
        double k5 = k3/k4 * k2 - k1, y3 = A[j].y/k4 * k2 - A[i].y; 
        a = y3/k5, b = (A[i].y-k1*a) / k2;
}

void Work(){
        memset(Used, 0, sizeof Used);
        int Ans = 0;
        scanf("%d%d", &N, &M);
        for(reg int i = 1; i <= N; i ++) scanf("%lf%lf", &A[i].x, &A[i].y);
        if(N == 1){ printf("%d\n", 1); return ; }
        for(reg int i = 1; i <= N; i ++){
                if(Used[i]) continue ;
                int max_cnt = 0, id = 0;
                for(reg int j = i+1; j <= N; j ++){
                        if(Used[j]) continue ;
                        int cnt = 0;
                        double a, b;
                        Calc(i, j, a, b);
                        if(a > 1e-14 || fabs(a) < 1e-14) continue ;
                        for(reg int k = 1; k <= N; k ++){
                                if(k == i || Used[k]) continue ;
                                double a1, b1;
                                Calc(i, k, a1, b1);
                                if(fabs(a1-a) < 1e-14 && fabs(b1-b) < 1e-14) cnt ++;
                        }
                        if(cnt > max_cnt) max_cnt = cnt, id = j;
                }
                if(id){ 
                        double a, b; 
                        Calc(i, id, a, b); 
                        for(reg int k = 1; k <= N; k ++){ 
                                if(k == i || Used[k]) continue ; 
                                double a1, b1; 
                                Calc(i, k, a1, b1); 
                                if(fabs(a1-a) < 1e-14 && fabs(b1-b) < 1e-14) Used[k] = 1; 
                        }
                }
                Used[i] = 1;
                Ans ++;
        }
        printf("%d\n", Ans);
}

int main(){
        int T;
        scanf("%d", &T);
        while(T --) Work();
        return 0;
}

模 拟 退 火 模拟退火 退 代码 ↓ 100 p t s ↓\color{red}{100pts} 100pts

#include
#define reg register

const int inf = 0x3f3f3f3f;
const int maxn = 25;
const double eps = 1e-14;

int N;
int M;
int Ans;
int tmp[maxn];

struct Bird{ double x, y; } A[25];
bool cmp(Bird a, Bird b){ return a.x < b.x; }

void Calc(int i, int j, double &a, double &b){ 
        double k1 = A[i].x*A[i].x, k2 = A[i].x; 
        double k3 = A[j].x*A[j].x, k4 = A[j].x; 
        double k5 = k3/k4 * k2 - k1, y3 = A[j].y/k4 * k2 - A[i].y; 
        a = y3/k5, b = (A[i].y-k1*a) / k2;
}

int Play(){
        int s = 0;
        double a = 0, b = 0;
        for(reg int i = 1; i <= N; i ++){
                int t1 = tmp[i], t2 = tmp[i+1];
                if(fabs(a) > eps && fabs( a*A[t1].x*A[t1].x + b*A[t1].x - A[t1].y ) < eps ) continue ;
                s ++; if(i == N) break ;
                if(fabs(A[t1].x - A[t2].x) < eps) continue ;
                Calc(t1, t2, a, b);
                if(a > eps || fabs(a) < eps) a = b = 0;
                else i ++;
        }
        return s;
}

void SA(){
        int res = Ans;
        double T = 250, delt = 0.99;
        while(T > 1e-6){
                int pos_1 = (rand()%N)+1, pos_2 = (rand()%N) + 1;
                while(pos_1 == pos_2) pos_2 = (rand()%N) + 1;
                std::swap(tmp[pos_1], tmp[pos_2]);
                int New_ans = Play();
                int Temp = New_ans - res;
                if(tmp < 0 || exp(-Temp/T)*RAND_MAX > rand()) res = New_ans, Ans = std::min(res, Ans);
                else std::swap(tmp[pos_1], tmp[pos_2]);
                T *= delt;
        }
}

void Work(){
        scanf("%d%d", &N, &M);
        for(reg int i = 1; i <= N; i ++) scanf("%lf%lf", &A[i].x, &A[i].y);
        if(N == 1){ printf("%d\n", 1); return ; }
        for(reg int i = 1; i <= N; i ++) tmp[i] = i;
        srand(92332322), srand(rand());
        std::random_shuffle(tmp+1, tmp+N+1);
        Ans = Play(); 
        for(reg int i = 1; i <= 5; i ++) SA();
        printf("%d\n", Ans);
}

int main(){
        int T;
        scanf("%d", &T);
        while(T --) Work();
        return 0;
}

状压dp 代码 ↓ ↓ 100 p t s \color{red}{100pts} 100pts

#include
#define reg register

const double eps = 1e-14;

int N;
int M;
int F[1<<19];
int p[1<<19];

struct Bird{ double x, y; } A[25];

void Calc(int i, int j, double &a, double &b){ 
        double k1 = A[i].x*A[i].x, k2 = A[i].x; 
        double k3 = A[j].x*A[j].x, k4 = A[j].x; 
        double k5 = k3/k4 * k2 - k1, y3 = A[j].y/k4 * k2 - A[i].y; 
        a = y3/k5, b = (A[i].y-k1*a) / k2;
}

void Work(){
        scanf("%d%d", &N, &M);
        int p_cnt = 0;
        for(reg int i = 1; i <= N; i ++) scanf("%lf%lf", &A[i].x, &A[i].y);
        if(N == 1){ printf("1\n"); return ; }
        memset(F, 0x3f, sizeof F), F[0] = 0;
        for(reg int i = 1; i <= N; i ++){
                int tmp = 0, last = p_cnt;
                for(reg int j = i+1; j <= N; j ++){ 
                        if((tmp>>j-1) & 1) continue ;
                        double a, b;
                        if(fabs(A[i].x-A[j].x) < eps) continue ;
                        Calc(i, j, a, b);
                        if(a > eps || fabs(a) < eps) continue ;
                        p[++ p_cnt] = (1 << i-1) | (1 << j-1);
                        for(reg int k = 1; k <= N; k ++)
                                if(fabs(A[k].x*A[k].x*a + A[k].x*b - A[k].y) < eps) p[p_cnt] |= 1 << k-1;
                        tmp |= p[p_cnt];
                }
                if(p_cnt == last) p[++ p_cnt] = 1 << i-1;
        }
        for(reg int i = 0; i < (1<<N); i ++)
                for(reg int j = 1; j <= p_cnt; j ++)
                        F[i|p[j]] = std::min(F[i|p[j]], F[i] + 1);
        printf("%d\n", F[(1<<N)-1]);
}

int main(){
        int T;
        scanf("%d", &T);
        while(T --) Work();
        return 0;
}

你可能感兴趣的:(Second,搜索-模拟退火,动态规划-状态压缩)