hdu 3120 dolphin

这个题很不错哦,用到了最短路+二分答案+dfs,出题人太厉害了

首先,100个点,总共的点的标号数目可能达到100,压缩不了,那就只能dfs了,但肯定需要剪枝

我自己想到的剪枝就是,先不管标号的问题,从终点做一次最短路,记录路径,如果源点不可达,则无解,如果这条路径上的点刚好标号都不一样,则输出到源点的最短路径

然后就暴力dfs,如果当前长度加上不考虑标号时当前点到终点的最短路径都大于当前最优解,就返回,这样还是T了

后来想能否快速找到一个可行解作为上限值,但不会找,查了解题报告才知道,可以二分答案,相当于有目的地找多次可行解,然后去验证,很巧妙

这题最让我觉得不可思议的就是每次dfs到一个点的时候都去求一次该点到终点的最短路,用来剪枝,好神奇啊

还有一个值得注意的地方就是,dfs的时候,在进入某个点之前把它标记,然后dfs回来时再撤销标记,否则,在进入某个点后标记,退出这个点时在撤销,可能会由于剪枝而忘记撤销标记的情况

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<stack>
#include<queue>
#include<vector>
#include<map>
#include<ctime>
using namespace std;
const int MAX=1005;
const int inf=1<<26;
struct node 
{
    int v,w,next;
}g[MAX*100];
int adj[MAX],e,vis1[MAX],vis2[MAX],kind[MAX],n,m;
int dis[MAX],fa[MAX],pre[MAX];
int flag[MAX];
bool pos[MAX],found;
void add(int u,int v,int w)
{
    g[e].v=v; g[e].w=w; g[e].next=adj[u]; adj[u]=e++;
}
void spfa(int s,int t,int k)
{
    int i,u,v;
    queue<int>que;
    for(i=0;i<=n;i++)
        dis[i]=inf;
    if(k)
    	memset(pre,-1,sizeof(pre));
    dis[s]=0;
    memset(vis1,0,sizeof(vis1));
    vis1[s]=1;
    que.push(s);
    while(!que.empty())
    {
        u=que.front();
        que.pop();
        vis1[u]=0;
        for(i=adj[u];i!=-1;i=g[i].next)
        {
            v=g[i].v;
            if(vis2[kind[v]])
                continue;
            //if(kind[v]==kind[t])
                //continue;
            //if(kind[v]==kind[s]&&v!=s)
                //continue;
            if(dis[v]>dis[u]+g[i].w)
            {
                dis[v]=dis[u]+g[i].w;
                pre[v]=u;
                if(!vis1[v])
                {
                    vis1[v]=1;
                    que.push(v);
                }
            }
        }
    }
}                
bool check()
{
    for(int i=0;i<MAX;i++)
        if(flag[i]>1)
            return false;
    return true;
}
bool dfs(int u,int now,int limit,int t)
{
    if(now>limit)
        return false;
    if(u==t)
        return true;
    spfa(u,t,0);
    if(now+dis[t]>limit)
        return false;
    int i,v;
    for(i=adj[u];i!=-1;i=g[i].next)
    {
        v=g[i].v;
        if(vis2[kind[v]])
            continue;
        vis2[kind[v]]=1;
        if(dfs(v,now+g[i].w,limit,t))
            return true;
        vis2[kind[v]]=0;
    }
    return false;
}
void solve(int s,int t,int sum)
{
    int l=1,r=sum,ans=-1,mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        memset(vis2,0,sizeof(vis2));
        vis2[kind[s]]=1;
        if(dfs(s,0,mid,t))
        {
            ans=mid;
            r=mid-1;
        }
        else
            l=mid+1;
    }
    printf("%d\n",ans);
}
inline int nextInt()
{
	int res = 0; char ch;
	bool neg = false;
	while (ch = getchar(), (ch < '0' || ch > '9') && ch != '-');
	if (ch == '-') neg = true;
	else res = ch - '0';
	while (ch = getchar(), ch >= '0' && ch <= '9') res = res * 10 + ch - '0';
	if (neg) res = - res;
	return res;
}
int main()
{
    int i,j,k,T,s,t,sum=0;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d%d",&n,&m,&s,&t);
        memset(adj,-1,sizeof(adj));
        e=0;
        while(m--)
        {
            //scanf("%d%d%d",&i,&j,&k);
            i=nextInt(); j=nextInt(); k=nextInt();
            add(i,j,k); add(j,i,k);
            sum+=k;
        }
        for(i=0;i<n;i++)
        {
            //scanf("%d",&kind[i]);
            kind[i]=nextInt();
        }
        if(s==t)
        {
            puts("0");
            continue;
        }
        memset(vis2,0,sizeof(vis2));
        vis2[kind[t]]=1;
        spfa(t,s,1);
        memset(flag,0,sizeof(flag));
        if(pre[s]==-1)
        {
            puts("-1");
            continue;
        }
        for(i=s;i!=-1;i=pre[i])
        {
            flag[kind[i]]++;
        }
        if(check())
        {
            printf("%d\n",dis[s]);
            continue;
        }
        solve(s,t,sum);
    }
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值