• 【FZYZOJ】愚人节礼物 题解(状压DP)


    前言:麻麻我会写状压DP了!

    ----------------------------

    题目描述

    愚人节到了!可爱的UOI小朋友要给孩子们送礼物(汗~原题不是可爱的打败图么= =..)。在平面直角坐标系上(欧氏的),有 N (N≤17)个点,分别代表 N 个小朋友的地理位置;还有一个点,这是UOI的家,UOI的所有礼物都放在这里,UOI必须驼着礼物从这里出发,送完礼物以后再返回这里,然后再拿出礼物出发。UOI身上每多驼一个礼物,他的速度就减半。已知他不驼礼物的速度是 V。为了不耽误(你懂的),UOI必须尽快完成这个任务。求完成任务的最短时间。

    输入格式

    第1行 1 个正整数,表示小朋友的个数。

    以下 N+1 行每行两个整数,除第一个表示UOI的家以外,其他都表示一个小朋友的坐标。

    最后一行一个整数 V,表示UOI的速度。

    输出格式

    输出一个整数,表示最短时间。四舍五入。

    ----------------------------------------------------------

    作为一道变形的状压DP题,其转移方程还是比较简单的(亏我好几个小时都没想到QAQ)。我们首先证明一个结论:

    要保证方案的最优性,每次最多只能携带两个礼物。

    证明:假设现在原点为$O$,平面上有$A,B,C$三个点。假如我们一次性走完,那么费用是$8OA+4AB+2BC+CO$;如果我们先走$A$点,再走$B$点和$C$点,那么费用是$3OA+4OB+2BC+CO$,两式相减,得$5OA+4AB-4OB=OA+4(OA+AB-OB)$,在三角形$OAB$中$OA+AB-OB>0$,所以相减之后的式子是大于$0$的。得证选三个点不优。对于大于三个点的情况,可以类比成多个三角形和线段组合成的图形,证明方法是类似的。证毕。复杂度$O(n^2 2^n)$

    状态转移方程:假设$1$表示不选,$0$表示选,则有:

    $f[k]=min (f[k],min(f[k|(1<<(i-1)|1<<(j-1)]+cost(i,j),f[k|1<<(i-1)]+cost(i))$

    约束条件:

    if (!1<<(i-1)&k&&!1<<(j-1)&k)

    注意开$long long$!!!!!!!!!!!(被这个卡了好久QAQ)

    代码:

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    int n,x[20],y[20],v;
    double f[1926817],ans,dis[20][20];
    double cal(int a,int b)
    {
        return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
    }
    double C1(int a,int b)
    {
        if (dis[0][a]<dis[0][b]) swap(a,b);
        return (4*dis[0][b]+2*dis[a][b]+dis[a][0])/v;
    }
    double C2(int i)
    {
        return 3*dis[i][0]/v;
    }
    signed main()
    {
        scanf("%lld",&n);
        scanf("%lld%lld",&x[0],&y[0]);
        for (int i=1;i<=n;i++) scanf("%lld%lld",&x[i],&y[i]);
        scanf("%lld",&v);
        for (int i=0;i<=n;i++)
            for (int j=i;j<=n;j++) dis[i][j]=dis[j][i]=cal(i,j);
        for (int i=(1<<n)-2;i>=0;i--)
        {
            f[i]=1e18;
            for (int j=1;j<=n;j++)
            {
                if ((1<<(j-1))&i) continue;
                for (int k=j+1;k<=n;k++)
                {
                    if ((1<<(k-1))&i) continue;
                    f[i]=min(f[i],f[i|(1<<(j-1))|(1<<(k-1))]+C1(j,k));
                }
                f[i]=min(f[i],f[i|(1<<(j-1))]+C2(j));
            }
        }
        printf("%.0lf",f[0]);
        return 0; 
    }
  • 相关阅读:
    【NX二次开发】修改dlx对话框标题的方法
    【NX二次开发】导入x_t,UF_PS_import_data
    设置NX欢迎界面
    [转]10个顶级的CSS UI开源框架
    [转] 多线程 《深入浅出 Java Concurrency》目录
    [转] JAVA多线程和并发基础面试问答
    [转]StuQ 技能图谱(全套13张)
    [转] MongoDB shell 操作 (查询)
    搜集好的java技术帖子,持续更新,java程序员的要求
    [转]JAVA程序员一定知道的优秀第三方库(2016版)
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/12978116.html
Copyright © 2020-2023  润新知