hdu 3920(状态压缩dp)

本文介绍了一个关于使用激光枪射击敌人并优化射击路径的算法问题。玩家需清除地图上的所有敌人,通过激光反射机制同时攻击两个目标,旨在找到最节省能量的射击方案。文章探讨了状态压缩动态规划的应用,并给出了两种不同的实现方式。

Clear All of Them I

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 122768/62768 K (Java/Others)


Problem Description
Acmers have been the Earth Protector against the evil enemy for a long time, now it’s your turn to protect our home.
  There are 2 * n enemies in the map. Your task is to clear all of them with your super laser gun at the fixed position (x, y).
  For each laser shot, your laser beam can reflect 1 times (must be 1 times), which means it can kill 2 enemies at one time. And the energy this shot costs is the total length of the laser path.
  For example, if you are at (0, 0), and use one laser shot kills the 2 enemies in the order of (3, 4), (6, 0), then the energy this shot costs is 5.0 + 5.0 = 10. 00.
  Since there are 2 * n enemies, you have to shot n times to clear all of them. For each shot, it is you that select two existed enemies and decide the reflect order.
  Now, telling you your position and the 2n enemies’ position, to save the energy, can you tell me how much energy you need at least to clear all of them?
  Note that:
   > Each enemy can only be attacked once.
   > All the positions will be unique.
   > You must attack 2 different enemies in one shot.
   > You can’t change your position.
 

Input
The first line contains a single positive integer T( T <= 100 ), indicates the number of test cases.
For each case:
  There are 2 integers x and y in the first line, which means your position.
  The second line is an integer n(1 <= n <= 10), denote there are 2n enemies.
  Then there following 2n lines, each line have 2 integers denote the position of an enemy.
  
  All the position integers are between -1000 and 1000.
 

Output
For each test case: output the case number as shown and then print a decimal v, which is the energy you need at least to clear all of them (round to 2 decimal places).
 

Sample Input
2 0 0 1 6 0 3 0 0 0 2 1 0 2 1 -1 0 -2 0
 

Sample Output
Case #1: 6.00 Case #2: 4.41
解题思路:首先这道题目最多只有20个状态,打中目标还有考虑别的目标是否打中,属于取与不取的问题,那么正好就是状态压缩能够解决的范畴。。dp[i]表示状态为i时的最小化费,那么一次能够打两个,那么下一个状态应该是i-(1<<j)-(1<<k),j,k分别代表先后击中的目标。最后的目标是dp[0](0代表该位置的目标被击中,1代表未被击中)。可惜最后TLE,后面仔细想想,应该还是在先被击中与后被击中的选择上循环次数太多啦。。。
TLE:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;

const int maxn = 55;
const double esp = 1e-8;
struct node
{
	double x,y;
}o,s[maxn];
int n;
double dp[(1<<20)+5],d[maxn][maxn],os[maxn];

double dis(node a,node b)  
{  
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));  
}  

int main()
{	
	int t,i,j,k,cas = 1;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%lf%lf%d",&o.x,&o.y,&n);
		n *= 2;
		for(i = 0; i<n; i++)
			scanf("%lf%lf",&s[i].x,&s[i].y);
		for(i = 0; i<n; i++)
			for(j = 0; j<n; j++)
			{
				d[i][j] = dis(s[i],s[j]);
			}
			for(i = 0; i<n; i++)
				os[i] = dis(o,s[i]);
			for(i = 0; i<(1<<n); i++)
				dp[i] = -1.0;
			dp[(1<<n)-1] = 0;
			for(i = (1<<n)-1; i >= 0; i--)
				for(j = 0; j < n; j++)	//第一次打到j
					for(k = 0; k < n; k++)	//第二次达到k
					{
						if(dp[i] == -1) continue;
						if(i & (1<<j) && i & (1<<k) && j != k)
						{
							int tmp = i - (1<<j) - (1<<k);
							if(dp[tmp] < esp || dp[tmp] > dp[i] + os[j] + d[j][k])
								dp[tmp] = dp[i] + os[j] + d[j][k];
						}
					}
			printf("Case #%d: %.2f\n",cas++,dp[0]);
	}
	return 0;
}


看了别人的分析:

所以状态的转移是由前往后递推的,假如当前状态是cur,上一个状态是last,应该满足存在i!=j有last&(1<<i)=0且last&(1<<j)=0且last | (1<<i)|(1<<j) = cur,由此来更新当前的状态cur,最后的答案就是DP[(1<<(2n)) - 1]

注意到上面我们是要枚举i和j的,所以这个的总复杂度就是20 * 20 * 2^20,这显然是会超时的, 所以需要优化

注意到如果存在两队人(a,b)(c,d)我们先打(a, b)再打(c, d)和先打(c,d)在打(a, b)是一样的,所以我们完全可以每次取last中最小的一位是0的与后面所有的0组合,这样不仅没有漏掉解,而且复杂度也将到了O(20 * 2^20)这就可以过了


AC:
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
#define exp 1e-8

struct node
{
    double x,y;
} o,s[50];

int n;
double d[50][50],dp[(1<<20)+1],os[50];

double dis(node a,node b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

int cmp(node a,node b)
{
    return dis(a,o)<dis(b,o);
}

double dfs(int u)
{
    if(dp[u]>exp) return dp[u];
    if(!u) return 0;
    int m = 0;
    while(!(u&(1<<m))) m++;
    int tem;
    for(int i = m+1; i<n; i++)
    {
        if(u&(1<<i))
        {
            tem = u - (1<<i) - (1<<m);
            double ans = dfs(tem)+os[m]+d[i][m];
            if(dp[u]<exp || ans<dp[u])
                dp[u] = ans;
        }
    }
    return dp[u]<exp?0:dp[u];
}

int main()
{
    int t,i,j,k,cas = 1;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lf%lf%d",&o.x,&o.y,&n);
        n*=2;
        for(i = 0; i<n; i++)
            scanf("%lf%lf",&s[i].x,&s[i].y);
        sort(s,s+n,cmp);
        for(i = 0; i<n; i++)
            for(j = 0; j<n; j++)
            {
                d[i][j] = dis(s[i],s[j]);
            }
        for(i = 0; i<n; i++)
            os[i] = dis(o,s[i]);
        for(i = 0; i<(1<<n); i++)
            dp[i] = -1.0;
        dfs(i-1);
        printf("Case #%d: %.2f\n",cas++,dp[i-1]);
    }

    return 0;
}


内容概要:本文系统介绍了算术优化算法(AOA)的基本原理、核心思想及Python实现方法,并通过图像分割的实际案例展示了其应用价值。AOA是一种基于种群的元启发式算法,其核心思想来源于四则运算,利用乘除运算进行全局勘探,加减运算进行局部开发,通过数学优化器加速函数(MOA)和数学优化概率(MOP)动态控制搜索过程,在全局探索与局部开发之间实现平衡。文章详细解析了算法的初始化、勘探与开发阶段的更新策略,并提供了完整的Python代码实现,结合Rastrigin函数进行测试验证。进一步地,以Flask框架搭建前后端分离系统,将AOA应用于图像分割任务,展示了其在实际工程中的可行性与高效性。最后,通过收敛速度、寻优精度等指标评估算法性能,并提出自适应参数调整、模型优化和并行计算等改进策略。; 适合人群:具备一定Python编程基础和优化算法基础知识的高校学生、科研人员及工程技术人员,尤其适合从事人工智能、图像处理、智能优化等领域的从业者;; 使用场景及目标:①理解元启发式算法的设计思想与实现机制;②掌握AOA在函数优化、图像分割等实际问题中的建模与求解方法;③学习如何将优化算法集成到Web系统中实现工程化应用;④为算法性能评估与改进提供实践参考; 阅读建议:建议读者结合代码逐行调试,深入理解算法流程中MOA与MOP的作用机制,尝试在不同测试函数上运行算法以观察性能差异,并可进一步扩展图像分割模块,引入更复杂的预处理或后处理技术以提升分割效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值