HDU 6005 Pandaland 最小环(dijkstra+剪枝)

Pandaland

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 745    Accepted Submission(s): 166

Problem Description

Mr. Panda lives in Pandaland. There are many cities in Pandaland. Each city can be treated as a point on a 2D plane. Different cities are located in different locations.
There are also M bidirectional roads connecting those cities. There is no intersection between two distinct roads except their endpoints. Besides, each road has a cost w.
One day, Mr. Panda wants to find a simple cycle with minmal cost in the Pandaland. To clarify, a simple cycle is a path which starts and ends on the same city and visits each road at most once.
The cost of a cycle is the sum of the costs of all the roads it contains.

Input

The first line of the input gives the number of test cases, T. T test cases follow.
Each test case begins with an integer M.
Following M lines discribes roads in Pandaland.
Each line has 5 integers x1,y1,x2,y2, w, representing there is a road with cost w connecting the cities on (x1,y1) and (x2,y2)

Output

For each test case, output one line containing Case #x: y, where x is the test case number (starting from 1) and y is the cost Mr. Panda wants to know.
If there is no cycles in the map, y is 0.

limits
∙1≤T≤50.
∙1≤m≤4000.
∙−10000≤xi,yi≤10000.
∙1≤w≤105.

Sample Input

2 5 0 0 0 1 2 0 0 1 0 2 0 1 1 1 2 1 0 1 1 2 1 0 0 1 5 9 1 1 3 1 1 1 1 1 3 2 3 1 3 3 2 1 3 3 3 1 1 1 2 2 2 2 2 3 3 3 3 1 2 2 1 2 2 1 3 2 4 1 5 1 4

Sample Output

Case #1: 8 Case #2: 4

Source

2016 CCPC-Final

 

 

 

        大致题意就是,给你一个图,然后让你在里面找一个最小的环。

        说到找最小环,我很惊叹网上居然没有一个很好的算法。见到的算法都是用floyd的O(N^3)或者dijkstra的O(N^2logN)。但是这些显然都不能满足这题的要求。然后其实本题还加了一个条件,就是一定是一个平面图。一开始还拼命地去想平面图的性质,现在发现其实这些都无所谓。

        这题,只要你联想到之前多校赛的第k小生成树的和,你就会知道如何下手。那题利用树边和非树边进行修改生成树和成环。这题也可以类似。由于要求是最小环,我们可以首先对这个图求最小生成树。根据树的性质,如果我们再添加上任意一条边,就会形成一个环。至于这个环的大小,我们可以用LCA来处理,计算出树上两点距离再加上边权结果就是这个环的大小。考虑最小环,我们只需要枚举这个边,然后找到最小的环即可。时间复杂度为O(V+E+NlogN),NlogN为排序。


upd:上面的方法是错误的,具体为什么看下面的评论就知道了。不好意思误导大家了,看来这个数据也比较水。正解的话还是下面那段,dijkstra的剪枝。

        事后看了下网上的题解,惊叹于居然大家都是dijkstra暴力做的,每次枚举一条边,然后把这条边删掉,边的两端设置为起点和终点,跑dijkstra。在跑的时候,每次dij到出现一个权值比当前ans要大的时候就停止dij。这样也是可以水过的,虽然理论是肯定会T的。具体见代码吧,感觉方法还是非常巧妙的,代码是队友写的,跟我风格不太符合:

#include<bits/stdc++.h>
#define N 8010
#define INF 100000000000
#define LL long long
using namespace std;
typedef pair<int,int> P;
LL i,j,k,l,n,t,h;
map<P,LL>s;
vector <int> a[N],b[N];
bool f[N];
LL L[N],fa[N],anc[N][20],d[N],m;

struct edge
{
    int a,b,c;
    bool operator < (const edge x)const
    {
        return c<x.c;
    }
}E[N];

void lable(LL x,LL ffa,LL p)						//LCA标号
{
    for (LL i=0;i<a[x].size();i++)if (a[x][i]!=ffa)
    {
        d[a[x][i]]=d[x]+b[x][i];
        fa[a[x][i]]=x;
        L[a[x][i]]=p+1;
        lable(a[x][i],x,p+1);
    }
}

void preprocess()						//倍增预处理
{
    for (LL i=1;i<=n;i++)
    {
        anc[i][0]=fa[i];
        for (LL j=1;(1<<j)<=n;j++) anc[i][j]=-1;
    }

    for (LL j=1;(1<<j)<=n;j++)
        for (LL i=1;i<=n;i++)
        if (anc[i][j-1]!=-1)
        {
            LL x=anc[i][j-1];
            anc[i][j]=anc[x][j-1];
        }
}

LL query(LL p,LL q)						//求两点LCA
{
    LL log,i;
    if (L[p]<L[q]) swap(p,q);
    for (log=1;(1<<log)<=L[p];log++);log--;
    for (LL i=log;i>=0;i--)
        if (L[p]-(1<<i)>=L[q])
        {
            p=anc[p][i];
        }
    if (p==q)return p; //LCA?ap

    for (LL i=log;i>=0;i--)
        if (anc[p][i]!=-1 && anc[p][i]!=anc[q][i])
    {
        p=anc[p][i];
        q=anc[q][i];
    }
    return fa[p];//LCA?afa[p]
}

int faa[N];
int getfa(int x)						//并查集……
{
    if (faa[x]==0)return x;else
    {
        int t=getfa(faa[x]);
        faa[x]=t;
        return t;
    }
}

bool used[N];

int main()
{
    LL T,tt=0;
    scanf("%I64d",&T);
    while (T--)
    {
        t=0;s.clear();
        memset(f,0,sizeof f);memset(d,0,sizeof d);memset(used,0,sizeof used);
        memset(a,0,sizeof a);memset(b,0,sizeof b);
        memset(faa,0,sizeof faa);memset(fa,0,sizeof fa);
        memset(L,0,sizeof L);memset(anc,0,sizeof anc);
        m=100000000000;
        scanf("%I64d",&n);
        for (i=1;i<=n;i++)
        {
            LL x,y;
            scanf("%I64d%I64d",&x,&y);
            if (!s[P(x,y)]) {j=++t;s[P(x,y)]=t;}else j=s[P(x,y)];
            scanf("%I64d%I64d",&x,&y);
            if (!s[P(x,y)]) {k=++t;s[P(x,y)]=t;}else k=s[P(x,y)];
            scanf("%I64d",&l);
            E[i]=edge{j,k,l};
        }
        sort(E+1,E+n+1);
        for (i=1;i<=n;i++)							//确定最小生成树,标记树边
        {
            int p=getfa(E[i].a),q=getfa(E[i].b);
            if (p!=q)
            {
                used[i]=1;
                int v=E[i].a,u=E[i].b;
                a[v].push_back(u);b[v].push_back(E[i].c);			//建树
                a[u].push_back(v);b[u].push_back(E[i].c);
                faa[p]=q;
            }
        }
        int nn=n;
        n=t;
        for (i=1;i<=n;i++)
        {
            if (fa[i]==0)
            {
                fa[i]=i;
                lable(i,-1,0);

            }
        }
        preprocess();
        for (i=1;i<=nn;i++)if (!used[i])					//枚举非树边求最小环
        {
            int v=E[i].a,u=E[i].b;
            LL w=d[v]+d[u]-d[query(v,u)]*2;
            if (w+E[i].c<m)m=w+E[i].c;
        }
        if (m==100000000000) m=0;
        printf("Case #%I64d: %I64d\n",++tt,m);
    }
    return 0;
}

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值