牛客网比赛177 旅游

题目大意

链接:https://www.nowcoder.com/acm/contest/177/B
来源:牛客网

暑假,可怜打算去旅游。

在可怜的计划中,可怜一共打算游玩 n n n 个景点,这些景点被 m m m 条双向道路联通(即任何两个景点之间都能通过道路直接或者间接到达)。第 i i i 条道路的长度为 2 i 2^i 2i

因为这 n n n 个景点中,只有 1 1 1 号景点在机场附近,所以可怜想要制定一个从 1 1 1 号点出发,沿着道路一路游玩,并在最后回到 1 1 1 号点的游览计划。同时因为每一条道路都有不一样的风景,于是可怜想要在这个计划中,经过每一条道路至少一次(只要从一个方向走过就算经过过这条道路)。

令一个游览计划的疲劳度为行走长度的总和(多次经过的边长度被多次计算),可怜想要计算所有满足条件的游览计划中疲劳度的最小值。

100 100 100%的数据 1 ≤ n , m ≤ 5 × 1 0 5 1 ≤ n , m ≤ 5 \times 10^5 1n,m5×105.

分析

首先,这道题肯定是要把每一条边都走一遍的。

那么,问题就转化成“怎样加入权值最小的边使得这个图是一个欧拉回路”。

怎样的图是欧拉回路呢?

在每个点的度都为偶数的图中,我们就可以从一个点仅遍历所有的边一次回到起点。

问题就变为“如何加入权值最小的边使得这个图每个点的度都为偶数”。

重新审视题面 "第 i i i 条道路的长度为 2 i 2^i 2i。"即我们选了一个i号边加入,即使选上前面所有的边,它们的权值的和都没有这第i号边大。我们考虑贪心的思想,尽量拿少的。

所以这个题就变为“如何选择加入这个图最小生成树的边使得这个图的每个点的度都为偶数。”

我们确定了最小生成树上的边是要选择加入的,那么加哪些可以使得每个点的度都为偶数呢?

我们考虑dfs遍历这棵最小生成树,从每个叶子结点讨论它是否为偶数。若叶子节点不为偶数,那么我们就加一条从此叶子结点到它父亲的边,并且将此叶子结点与其父亲结点的度都加上 1 1 1

参考代码

//
//  main.cpp
//  B-旅游
//
//  Created by Ted_Tong on 2018/10/14.
//  Copyright © 2018 Ted_Tong. All rights reserved.
//
 
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxm 500010
#define mod 998244353
#define ll long long
using namespace std;
int n,m,cnt_e,cnt_p,root,t,fa[maxm],ind[maxm],head[maxm];
bool vis[maxm];
ll ans;
struct edge{
    int u,v,w;
}edges[maxm*2];//edge存的是原图
bool cmp(edge a,edge b){
    return a.w<b.w;
}
void add_e(int u,int v,int w){
    cnt_e++;
    edges[cnt_e].u=u;
    edges[cnt_e].v=v;
    edges[cnt_e].w=w;
}
struct path{
    int to,nxt,w;
}paths[maxm*2];//path存的是最小生成树
void add_p(int u,int v,int w){
    cnt_p++;
    paths[cnt_p].to=v;
    paths[cnt_p].nxt=head[u];
    paths[cnt_p].w=w;
    head[u]=cnt_p;
}
int find(int x){
    if (fa[x]==x) {
        return fa[x];
    }
    return fa[x]=find(fa[x]);
}
ll quickPower(int b){
    ll res=1,base=2;
    while(b>0){
        if(b&1){
            res*=base;
            res%=mod;
        }
        base*=base;
        base%=mod;
        b>>=1;
    }
    return res;
}
void kruskal(){
    for (int i=1; i<=2*m; i++) {
        int fa_u=find(edges[i].u);
        int fa_v=find(edges[i].v);
        if (fa_u==fa_v) {
            continue;
        }
        add_p(edges[i].u,edges[i].v,edges[i].w);
        add_p(edges[i].v,edges[i].u,edges[i].w);
        fa[fa_v]=fa_u;
        t++;
        if (t==n-1) {
            root=find(fa_u);
            break;
        }
    }
}
void dfs(int pi,int u,int fa){
    for (int i=head[u]; i; i=paths[i].nxt) {
        int v=paths[i].to;
        if (!vis[v]) {
            vis[v]=1;
            dfs(i, v, u);
        }
    }
    if (ind[u]%2==1) {
        ind[u]++;
        ind[fa]++;
        ans=(ans+quickPower(paths[pi].w))%mod;
    }
    return;
}
int main() {
    scanf("%d%d",&n,&m);
    for (int i=1; i<=n; i++) {
        fa[i]=i;
    }
    for (int i=1; i<=m; i++) {
        int u,v;
        scanf("%d%d",&u,&v);
        add_e(u, v, i);
        add_e(v, u, i);
        ind[v]++;
        ind[u]++;
        ans=(ans+quickPower(i))%mod;
    }
    kruskal();
    vis[root]=1;
    dfs(0, root, 0);
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值