NOIP2017D2T2-宝藏

本文介绍了一种利用模拟退火算法解决特定图论问题的方法,即如何在给定的图中选择最优路径以实现最小化的工程总代价。通过枚举边与使用优先队列进行搜索,最终找到全局最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋, 也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。
新开发一条道路的代价是:
L*K
L代表这条道路的长度,K代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值。
输入格式
第一行两个用空格分离的正整数 n,m ,代表宝藏屋的个数和道路数。
接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏 屋的编号(编号为 1−n ),和这条道路的长度 v 。
输出格式
一个正整数,表示最小的总代价。
样例输入

4 5 
1 2 1 
1 3 3 
1 4 1 
2 3 4 
3 4 1 

样例输出

4

思路分析:

先说一个40分思路:
1.先建图输入数据
2.二进制枚举边
3.枚举时用并查集检验是否联通
4.联通就枚举起点BFS,更新ans
结果4组数据TLE,2组WA
原因:n个节点最多有n(n-1)/2条边,n最大12所以边最多66,二进制就是2^(66),数据量过大,例如#6数据需要15.97s但是输出是正解,做题的时候想成2^(12)< 1W,于是就枚举了。。。
WA原因:如图可能对于当前节点距离近的不如距离远,但是对于下一个节点距离近的好
这里写图片描述
不过最不开心的是直接就最小生成树是45分。。。
然后是AC思路:退火模拟

AC代码

#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>

#define inf 2147483647

using namespace std;
int n, m;
int map[13][13];int depth[13]; 
struct edge {
    int u, v;
}; bool operator < (struct edge a, struct edge b) {
    return depth[a.u]*map[a.u][a.v]>depth[b.u]*map[b.u][b.v];
}
int search(int source) {
    memset(depth, 0, sizeof(depth));
    int vis[13]={0};
    priority_queue <struct edge> heap;
    edge past[1000]; int p = 0;
    struct edge e, e2;
    int cost = 0;
    depth[source]=1; vis[source]=1;
    for (int i = 1; i <= n; ++i) {
        if (map[source][i]<inf) {
            e.u=source; e.v=i;
            heap.push(e);
        }
    } for (int i = 1; i < n; ++i) {
        e=heap.top(); heap.pop();
        while (!heap.empty()&&((vis[e.v]||rand()%(n)<1))) {//注意这里的判断条件rand()%n<1,即对于一个当前最近点,不选择的几率随着n的增大而减小。
            if (!vis[e.v]) past[p++]=e;
            //对于跳过了的边,以后还用得上,等待选择结束后再压回优先队列中
            e=heap.top(); heap.pop();

        } vis[e.v]=1; depth[e.v]=depth[e.u]+1;
        if (p-->0) { //压回优先队列
            for (;p>=0;--p) {
                heap.push(past[p]);
            }
        }p=0;
        for (int i = 1; i <= n; ++i) {
            if (map[e.v][i]<inf&&!vis[i]) {
                e2.u=e.v; e2.v=i;
                heap.push(e2);
            }
        } cost+=map[e.u][e.v]*depth[e.u];
    } return cost;
}

int main() 
{
    int a, b, c;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            map[i][j]=inf;
        }
    }
    for (int i = 0; i < m; ++i) {
        scanf("%d %d %d", &a, &b,&c);
        map[a][b]=map[b][a]=min(c, map[a][b]);
    } srand(201208);//瞎写的一个数,应该选什么数都差不多 
    int MIN = inf;
    for (int j = 1; j <1000; ++j) {
    //1000次运行是绝对万无一失的,事实上,400次就够了
        for (int i=1;i<=n; ++i) {
            MIN=min(MIN, search(i));
        }
    }printf("%d", MIN);
    return 0;
}

40分代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
#include <string>
using namespace std;
const int MAX_M=1050;           //边的最大值 
const int MAX_N=20;             //点的最大值 
const int INF=0x3f3f3f3f;
int m, n, ans = INF,cnt,input[MAX_N][MAX_N];

//-------------------------------------------------------------------图
struct edge{
    int u,v,w,next;
}e[MAX_M];

int p[MAX_N],eid;

void Tinit(){
    memset(p,-1,sizeof(p));
    eid=0;
    return;
}

void insert(int u,int v,int w){
    e[eid].u=u;
    e[eid].v=v;
    e[eid].w=w;
    e[eid].next=p[u];
    p[u]=eid++;
    return;
}

void insert2(int u,int v,int w){
    insert(u,v,w);
    insert(v,u,w);
}
//------------------------------------------------------------------构建小图 
struct e2edge{
    int u,v,w,next;
}e2e[MAX_M];

int e2p[MAX_N],e2eid;

void e2Tinit(){
    memset(e2p,-1,sizeof(e2p));
    e2eid=0;
    return;
}

void e2insert(int u,int v,int w){
    e2e[e2eid].u=u;
    e2e[e2eid].v=v;
    e2e[e2eid].w=w;
    e2e[e2eid].next=e2p[u];
    e2p[u]=e2eid++;
    return;
}

void e2insert2(int u,int v,int w){
    e2insert(u,v,w);
    e2insert(v,u,w);
} 
//------------------------------------------------------------------并查集
int dad[MAX_N];
void Binit (){
    for(int i=0;i<MAX_N;i++)dad[i]=i;
    return;
} 

int Bgetdad(int i){
    if(dad[i]==i)return i;
    return dad[i]=Bgetdad(dad[i]);
}

//---------------------------------------------------------------------BFS
int d[MAX_N];

void BFS(int st){
    int tans=0;
    memset(d,INF,sizeof(d));
    queue<int>q;
    q.push(st);
    d[st]=0;
    while(!q.empty()){
        int tmp=q.front();
        q.pop();
        for (int i=e2p[tmp]; i!= -1; i = e2e[i].next) {
            if(d[e2e[i].v]==INF){
            d[e2e[i].v]=d[tmp]+1;
          q.push(e2e[i].v);
          tans+=d[e2e[i].v]*e2e[i].w;
            }
        }
    }
    ans=min(ans,tans);
}

//--------------------------------------------------------------------------main

int main(){
    memset(input,INF,sizeof(input));
    Tinit();
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int a,b,v;
        cin>>a>>b>>v;
        if(a<b){int t=a;a=b;b=t;}
        input[a][b]=min(input[a][b],v);
    }
    for(int i=1;i<=n;i++){
        for(int j=i-1;j>=1;j--){
            if(input[i][j]!=INF){
            insert2(i,j,input[i][j]);
            cnt++;
            }
        }
    }
    cout<<cnt;
    int debuguse=0;
    for(int i=0;i<=(1<<cnt);i++){
        e2Tinit();
        Binit();
        for(int j=0;j<=cnt;j++){
            if(i&(1<<j)){
                if(Bgetdad(e[j*2].u)!=Bgetdad(e[j*2].v))dad[Bgetdad(e[j*2].u)]=Bgetdad(e[j*2].v);//双向图 
                e2insert2(e[j*2].u,e[j*2].v,e[j*2].w);
            }
        }
        int t=Bgetdad(1);
        for(int i=1;i<=n;i++)if(Bgetdad(i)!=t){t=-1;}
        if(t==-1){continue;}
        for(int m=1;m<=n;m++){debuguse++;BFS(m);}   
    }
    cout<<ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liukairui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值