E 旅游方案(travel)(南阳理工学院2019年校赛)

E 旅游方案(travel)

小 C 是一位著名的旅游家。他准备规划他在 K 国的旅游路线。

K 国有若干座城市,每个城市有一个唯一的编号。城市和城市之间有一些道路相连,每条 道路只在某一天开放。小 C 的旅游路线有包括三个参数:u, v, t,表示小 C 准备在第 t 天旅 游,路线的起点为城市 u,终点为城市 v。此外,旅游路线还必须满足以下要求:

• u < v,即出发城市的编号必须比终点城市的编号小;

• 在第 t 天中,小 C 能够沿着开放的道路从 u 走到 v。

请帮小 C 计算出不同的旅游方案数,即满足上述条件的三元组 (u, v, t) 的个数。小 C 只关 注路线的起点和终点,也就是说,即使在某一天小 C 能够以多种方式从起点城市 u 到终点城 市 v,在计算方案数时只计算一次。但是,如果能够在不同的几天从起点城市 u 到终点城市 v,在计算方案数时需要计算多次。

例如,假设 K 国有 3 座城市和 3 条道路,编号分别为 1,2,3,第一条道路连接城市 1 和 城市 2,第二条道路连接城市 2 和城市 3,第三条道路连接城市 1 和城市 3。其中,第 1 和第 2 条道路只在第 1 天开放,第 3 条道路只在第 2 天开放,如下图所示。

img

合法的旅游方案共有 4 种:

  1. 在第 1 天,以城市 1 为起点,城市 2 为终点;

  2. 在第 1 天,以城市 1 为起点,城市 3 为终点;

  3. 在第 1 天,以城市 2 为起点,城市 3 为终点;

  4. 在第 2 天,以城市 1 为起点,城市 3 为终点。

输入描述:

输入一个整数 n, m (2 ≤ n ≤ 100000, 1 ≤ m ≤ 100000),表示城市的数量和道路的数量。城市的编号从 1 到 n。

接下来的 m 行,每行包括三个整数 u, v, t (1 ≤ u < v ≤ n, 1 ≤ t ≤ 100000),表示在城市 u和城市 v 之间有一条双向道路,这条道路只在第 t 天开放。在同两座城市之间可能存在多条道路。

输出描述:

在一行内输出一个整数,表示不同的旅游方案数。

样例输入:

3 3
1 2 1
2 3 1
1 3 2

样例输出:

4

提示:

img

分析:

​ 对于每一天可以通行的边,建立一个图,求出该图每一个联通块的顶点个数。对于每 n n n个节点的联通块, 从​ u u u到 ​ v v v 的路径有​ n ∗ ( n − 1 ) / 2 n*(n-1)/2 n(n1)/2种 ,用ans将每天的每个联通块的可行路径数加起来即可。具体实现可分下列步骤

  1. 对所有的边,根据天数从小到大排序 (方便找到某一天的所有边的区间)
  2. 对于每一天的的所有边,用并查集的方法在祖先节点记录该集合的顶点数
  3. 在第二部的基础上,遍历所有这一天所有出现边的顶点,如果该顶点是祖先就统计该集合的顶点数 n n n,从而把 n ∗ ( n − 1 ) / 2 ​ n*(n-1)/2​ n(n1)/2 加到ans上。

##代码:

#include<bits/stdc++.h>
#define mset(a,b)   memset(a,b,sizeof(a))
using namespace std;
const int maxn=1e5+20;
struct Node{
int u,v,t;
}edge[maxn];
bool cmp(Node a,Node b)
{
    return a.t<b.t;
}
int book[maxn];
int father[maxn],tot[maxn];//并查集  tot代表个数
int Find(int x)
{
    if(x==father[x])
        return x;
    return father[x]=Find(father[x]);
}
ll calc(ll a)
{
    return a*(a-1)/2;
}
ll solve(int l,int r)//对该区间内进行求解
{
    for(int i=l;i<=r;++i)//初始化
    {
        father[edge[i].u]=edge[i].u;
        father[edge[i].v]=edge[i].v;
        tot[edge[i].u]=1;
        tot[edge[i].v]=1;
    }
    /*并查集*/
    for(int i=l;i<=r;++i)
    {
        int root1,root2;
        root1=Find(edge[i].u);
        root2=Find(edge[i].v);
        if(root1!=root2)
        {
            tot[root1]+=tot[root2];
            father[root2]=root1;
        }
    }
    ll ans=0;
    for(int i=l;i<=r;++i)
    {
        int u=edge[i].u;
        if(father[u]==u&&tot[u]!=0)
        {
            ans+=calc(tot[u]);
            tot[u]=0;
        }
        u=edge[i].v;
        if(father[u]==u&&tot[u]!=0)
        {
            ans+=calc(tot[u]);
            tot[u]=0;
        }
    }
    return ans;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;++i)
    {
        scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].t);
    }
    sort(edge,edge+m,cmp);
    int l=0;
    ll ans=0;
    for(int i=0;i<m;++i)
    {
        if(i==m-1||edge[i].t!=edge[i+1].t)
        {
            ans+=solve(l,i);
            l=i+1;
        }
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值