【题解】NOIP-2017宝藏

Problem

洛谷

题目概要:
给定一张无向连通图,求一棵权值最小的有向生成树
权值定义为:从根开始,不断选择联通已选点 u u 与未选点v的一条边,连边花费为从根到 u u 的节点数乘以边的权值,公式表达为uvdad(u)dis(root,u)wuv

100% 100 % 数据范围: n12 n ≤ 12

Solution

对于 20% 20 % 的数据,图是一棵树,只有一棵生成树,模拟即可

对于 40% 40 % 的数据,所有边的权值相等,明显可以用贪心做,每次将最近点连接的所有未选点选上

考虑满分做法:

见过那么多出题人出数据卡暴力(尤其是NOIPday2T1不卡未合并前的并查集),明显出题人不会让我们的贪心拿到本该拿不到的分,从出题人角度出发,他会让我们每步选取最优失效,所以我们就故意不选最优,明显想到模拟退火,可以对于堆里的元素一定概率略过,选取堆顶以下的,即选取最优解的概率为 a a ,则取次优解的概率为a2,这样整体仍然保持贪心的性质,又能避开出题人卡我们的数据

可以玄学调参,比如

a得分
10% 10 % 45 45
15% 15 % 85 85
20% 20 % 90 90
21% 21 % 100 100

Code

#include<bits/stdc++.h>
using namespace std;
#define rg register
#define cl1(x) memset(x,-1,sizeof(x))

template <typename _Tp> inline _Tp read(_Tp&x){
    char c11=getchar(),ob=0;x=0;
    while(c11^'-'&&!isdigit(c11))c11=getchar();if(c11=='-')c11=getchar(),ob=1;
    while(isdigit(c11))x=x*10+c11-'0',c11=getchar();if(ob)x=-x;return x;
}

const int N=13,M=100,cnt=40,orz=21;
int mp[N][N],dis[N];
int n,m,top;
struct node{
    int u,v;
    inline node(){}
    inline node(const int&U,const int&V){u=U,v=V;}
    inline int d(){return dis[u]*mp[u][v];}
}rub[M],h[M],las;

inline void push(node newnode){
    h[++top]=newnode;
    int pp(top);
    while(pp>1&&h[pp].d()<h[pp>>1].d())swap(h[pp],h[pp>>1]),pp>>=1;
    return ;
}

inline void pop(){
    h[1]=h[top--];
    int pp(1);
    while(((pp<<1)<=top&&h[pp].d()>h[pp<<1].d())||((pp<<1|1)<=top&&h[pp].d()>h[pp<<1|1].d())){
        pp<<=1;
        if(h[pp].d()>h[pp|1].d())pp|=1;
        swap(h[pp],h[pp>>1]);
    }return ;
}

inline int random(int x){return rand()%100<x;}

int work(int bas){
    for(rg int i=1;i<=n;++i)dis[i]=-1;
    dis[bas]=1;int res(0);top=0;
    for(rg int i=1;i<=n;++i)if(~mp[bas][i])push(node(bas,i));

    for(rg int j=1;j<n;++j){
        int ruf(0);las=h[1];pop();
        while(top&&(~dis[las.v]||random(orz))){
            if(-1==dis[las.v])rub[++ruf]=las;
            las=h[1];pop();
        }
        while(ruf)push(rub[ruf--]);
        res+=las.d();
        int x=las.v;
        dis[x]=dis[las.u]+1;
        for(rg int i=1;i<=n;++i)
            if(~mp[x][i]&&-1==dis[i])
                push(node(x,i));
    }return res;
}

void init(){
    int x,y,z;
    read(n);read(m);
    cl1(mp);
    for(rg int i=0;i<m;++i){
        read(x);read(y);read(z);
        if(z<mp[x][y]||mp[x][y]==-1)
            mp[x][y]=mp[y][x]=z;
    }return ;
}

int main(){
    srand(18890420);
    init();int ans(0x7fffffff);
    for(int j=cnt;j;--j)
    for(int i=n;i;--i)
        ans=min(ans,work(i));
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值