Vijos P1228 拯救世界-星际大战

P1228拯救世界-星际大战
标签: [显示标签]

描述

外星人逐渐逼近,为了保护地球,现在决定直接在外空进行战斗。

现在我们有N个导弹。需要在最短的时间内,用这N个导弹摧毁敌方n个目标(1个导弹只能摧毁1个目标)。N个导弹和目标的位置不一定相同,但是给每个导弹确定目标是一件很麻烦的事情。请你编程帮助给每个导弹确定目标,使每个导弹到其目标的距离之和最小。

格式

输入格式

第一行输入N(N<=20)
接下来N行每行包含一个坐标(x,y),表示一个导弹,-10000<x,y<10000,且x,y为整数。

接下来N行每行包含一个坐标(x,y),表示一个目标,-10000<x,y<10000,且x,y为整数。

输出格式

每个导弹到其目标距离之和的最小值。结果保留3位小数。

样例1

样例输入1[复制]

1
10 1
6 -1

样例输出1[复制]

4.472


一开始是这样想的(错误):我先预处理出来任意两个点之间的距离,那么就是一个n*n的矩阵,(n <= 20)
那么问题转化为在这个矩阵上面选不同行不同列的n个点,且这n个点的和最小
那么就用区间DP来写
设dp[i][j][k]设以点i,j为起点,边长为k的方形中取k个点所得的最小值,那么我以为任意一个正方形都可以转化为两个成对角的小方形拼起来的,(满足不同行不同列)(如图左)
然而这个是错误的观点,反例见右图。。。。
可怜我写了半天,居然还过了5个测试点
比赛时候想法如果错了才坑
错误代码见我的代码片


正解:设DP[i][j]为用到第i个导弹时候,目标状态为j时的最优解(目标状态用二进制编码,1 代表打过,0代表没打过)
这样的话就很简单了,,
详情见代码

据说可以用随机化搜索搞
也可以用二分图




评测结果

编译成功

测试数据 #0: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #1: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #2: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #3: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #4: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #5: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #6: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #7: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #8: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

测试数据 #9: Accepted, time = 0 ms, mem = 209880 KiB, score = 10

Accepted, time = 0 ms, mem = 209880 KiB, score = 100

代码:
#include 
     
     
      
      
#define maxn (1 << 20) + 100
#define inf 0x3f3f3f3f
using namespace std;

struct Node {
    double x;
    double y;
} a[25],b[25];

double dp[25][maxn];
int cnt[maxn];

int Count1(int n) {
    int cnt = 0;
    while(n != 0) {
        if(n % 2 == 1)  cnt ++;
        n /= 2;
    }
    return cnt;
}

double Dis(Node p,Node q) {
    return sqrt((q.x - p.x)  * (q.x - p.x) + (q.y - p.y) * (q.y - p.y));
}

int main() {
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int n;
    scanf("%d",&n);
    for(int i = 1; i <= (1 << n) - 1; i ++)   cnt[i] = Count1(i);
    for(int i = 1; i <= n; i ++)  scanf("%lf%lf",&a[i].x,&a[i].y);
    for(int i = 1; i <= n; i ++)  scanf("%lf%lf",&b[i].x,&b[i].y);
    for(int i = 0; i < n; i ++)  dp[1][1 << i] = Dis(a[1],b[i + 1]);
    for(int i = 2; i <= n; i ++) {
        for(int j = 1; j <= (1 << n) - 1; j ++) {
            dp[i][j] = inf;
            if(cnt[j] == i) {
                for(int k = 0; k < n; k ++) {
                    if(((1 << k) & j) == 0) continue;
                    int last = j - (1 << k);
                    dp[i][j] = min(dp[i][j],dp[i - 1][last] + Dis(a[i],b[k + 1]));
                }
            }
        }
    }
    printf("%.3lf\n",dp[n][(1 << n) - 1]);
    return 0;
}

     
     

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值