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

愤怒的小鸟


Description\mathcal{Description}

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

N<=18N<=18


Solution\mathcal{Solution}

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

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


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

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


退 208ms模拟退火\ 208ms,dp 2608ms状压dp\ 2608ms (滑稽


实现部分
没什么好说的.


Code\mathcal{Code}

贪心 代码  45pts↓\ 45pts

#include<bits/stdc++.h>
#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;
}

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

#include<bits/stdc++.h>
#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 代码 100pts\color{red}{100pts}

#include<bits/stdc++.h>
#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;
}
posted @ 2019-06-29 09:43  XXX_Zbr  阅读(172)  评论(0编辑  收藏  举报