bzoj2753: [SCOI2012]滑雪与时间胶囊

链接

https://www.lydsy.com/JudgeOnline/problem.php?id=2753

题解

图论题好啊
最后它能滑倒的点,用一次 dfs d f s 就求出来了
这些点肯定每个都要去,只是你要计算从怎样的路径去
如果没有高度相同的点,那么这就是个 DAG D A G
对于每个点,你会发现,反正它的每一个前驱都要到达(我已经DFS预处理了),那我直接选择一条最小的边连上去不就行了
但是现在有高度相同的点,这个怎么做?
其实很容易,每个联通分量缩点,内部做最小生成树,整体还是 DAG D A G ,做一遍贪心就行了
实现上有技巧,我把所有的边排序,第一关键字是边的末尾节点的高度(递减),第二关键字为边权(递增)
这样排序完了,我直接整体做 kruskal k r u s k a l 就是对的
因为考虑一条边(u,v),如果 hu=hv h u = h v ,那么我直接做 krukal k r u k a l 就符合我的要求,如果 hu!=hv h u ! = h v ,那么因为我按照排序了,所以它就对应了从上面连下来的边权最小的边,直接 join j o i n 到答案里去也符合算法的要求

代码

//kruskal
#include 
#include 
#include 
#include 
#include 
#define ll long long
#define int_inf 0x3f3f3f3f
#define maxn 1000010
using namespace std;
int N, M, h[maxn], f[maxn], tot, head[maxn], to[maxn<<1], nex[maxn<<1], w[maxn<<1], m, vis[maxn];
ll cost;
struct edge{int u, v, w;}e[maxn<<1];
bool operator<(edge e1, edge e2){return h[e1.v]==h[e2.v]?e1.wh[e2.v];}
int read(int x=0)
{
    int c, f=1;
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-48;
    return f*x;
}
void adde(int a, int b, int c){to[++tot]=b;w[tot]=c;nex[tot]=head[a];head[a]=tot;e[tot].u=a;e[tot].v=b;e[tot].w=c;}
void dfs(int pos)
{
    int p;
    vis[pos]=1;
    for(p=head[pos];p;p=nex[p])
        if(!vis[to[p]])dfs(to[p]);
}
void init()
{
    int i, a, b, c, x;
    N=read(), M=read();
    for(i=1;i<=N;i++)h[i]=read();
    for(i=1;i<=M;i++)
    {
        a=read(), b=read(), c=read();
        if(h[a]if(h[a]>h[1])continue;
        adde(a,b,c);
        if(h[a]==h[b])adde(b,a,c);
    }
    dfs(1);
    for(i=1,x=0;i<=tot;i++)if(vis[e[i].u] and vis[e[i].u])e[++x]=e[i];
    tot=x;
    sort(e+1,e+tot+1);
    for(i=1;i<=N;i++)f[i]=i;
}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void work()
{
    int i, cnt=1, j;
    for(i=1;i<=tot;i++)if(find(e[i].u)^find(e[i].v))cost+=e[i].w, cnt++, f[find(e[i].u)]=find(e[i].v);
    printf("%d %lld",cnt,cost);
}
int main()
{
    init();
    work();
    return 0;
}

你可能感兴趣的:(图论)