hdu 4568

最短路 + DP

把边界看成一个点和其他treasure点构成了k+1个点,然后求出这k+!个点,任意两点之间的最短路径(因为任何一个最优方案中的边肯定是最短路径),一个图就确定了(可能两个点之间就不可到达,也不需要管,因为他们的最短路为正无穷大),我用的SPFA,用Djikstra写了超时吗,可能是哪没写好。然后可知题目让求的就是从边界这个点访问最多的点并且返回所用的最短路径。也就是经典的TSP问题的应用。先假定这k+1个点是相互联通的,不联通的暂且看做联通。长度为正无穷大。然后,求出边界点访问所有不同的集合返回时所用的最短路径。如果所求结果>=inf,就不考虑这种情况(很显然,最短的路程都为无穷大。那说明此集合本来就不联通)。然后比较得到集合中所含点最多并且所需路径最短的结果,也就是最后所求的答案。

做的时候很多细节问题考虑了很久。从中午写到晚上10点,还好一次AC。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
#include <queue>
using namespace std;
const int maxn=200+10;
const int inf=50000000;
int dir[4][2]={{0,1},{0,-1},{-1,0},{1,0}};
int cost[maxn][maxn];
int dis[15][15],vis[maxn][maxn];
int tdis[maxn*maxn],inq[maxn*maxn];
int loc[15*15];
int f[15][(1<<15)],num[1<<15];
int n,m,k;
queue <int> Q;
void bellmanford()
{
    int i,j;
    for(i=0;i<k;i++)
    {
        for(j=0;j<n*m;j++) tdis[j]=inf;
        memset(inq,0,sizeof(inq));
        tdis[loc[i]]=0;
        inq[loc[i]]=1;
        Q.push(loc[i]);//队列中存的是更新了的点
        int floc,tloc,fx,fy,tx,ty;
        while(!Q.empty())
        {
            floc=Q.front(); Q.pop();
            inq[floc]=0;
            fx=floc/m; fy=floc%m;
            for(j=0;j<4;j++)
            {
                tx=fx+dir[j][0]; ty=fy+dir[j][1];
                tloc=tx*m+ty;
                if(tx>=0&&tx<n&&ty>=0&&ty<m)
                {
                    if(tdis[floc]+cost[tx][ty]<tdis[tloc])
                    {
                        tdis[tloc]=tdis[floc]+cost[tx][ty];
                        if(!inq[tloc])
                        {
                            inq[tloc]=1;
                            Q.push(tloc);
                        }
                    }
                }
            }
        }
        dis[i][k]=inf;
        for(j=0;j<n*m;j++)
        {
            tx=j/m; ty=j%m;
            if(vis[tx][ty]!=-1) dis[i][vis[tx][ty]]=tdis[j];
            if(tx==0||tx==n-1||ty==0||ty==m-1) dis[i][k]=min(dis[i][k],tdis[j]);
        }
        dis[k][i]=dis[i][k]+cost[loc[i]/m][loc[i]%m];
    }
}
int main()
{
    int T;
    cin>>T;
    int i,j;
    num[0]=0;
    for(i=1;i<(1<<13);i++)
        num[i]=num[i>>1]+((i&1)?1:0);
    while(T--)
    {
        memset(vis,-1,sizeof(vis));
        scanf("%d%d",&n,&m);
        int j;
        for(i=0;i<n;i++)
        {
            for(j=0;j<m;j++)
            {
                scanf("%d",&cost[i][j]);
                if(cost[i][j]==-1) cost[i][j]=inf;
            }
        }
        scanf("%d",&k);
        int x,y;
        for(i=0;i<k;i++)
        {
            scanf("%d%d",&x,&y);
            vis[x][y]=i;
            loc[i]=x*m+y;
        }
        bellmanford();
        dis[k][k]=0;
        for(i=0;i<=k;i++) f[i][0]=dis[i][k];
        int w;
        int amount=0,ans=inf;
        for(i=1;i<(1<<k);i++)
        {
            for(j=0;j<=k;j++)
            {
                if(((1<<j)&i)==0)
                {
                    f[j][i]=inf;
                    for(w=0;w<k;w++)
                    {
                        if((1<<w)&i)
                        {
                            f[j][i]=min(f[j][i],f[w][i^(1<<w)]+dis[j][w]);
                        }
                    }
                }
            }
            if(f[k][i]<inf&&num[i]>=amount)
            {
                if(num[i]>amount)
                {
                ans=f[k][i];
                amount=num[i];
                }
                else if(f[k][i]<ans) ans=f[k][i];
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值