Codeforces Round #548 C. Edgy Trees

题面:

传送门 

题目描述:

给出有n个节点的树,整数k。题目要求找长度为k,符合规则(good序列)的“点序列”(由节点构成的序列)个数有多少?规则如下:
  • 1.走一条出发点为a1,终点为ak的一条路(允许重复边,重复点)
  • 2.从a1开始,通过最短路径走到a2,然后从a2通过最短路径走到a3,以此类推,直到走到终点
  • 3.如果在上述过程中,至少经过一条“黑边”,则这个序列是good的
 

题目分析:

这道题直接分析确实挺难,难在哪里呢?我们看看这个good序列要满足什么条件:
1.走一条路:这里要注意的就是可以重复点,其他没什么可以引起注意的地方
2.从a1走到a2,a2走到a3......如果这个过程经过了黑边,这个序列就是good序列:
所以刚开始我们的想法是:
找一条黑边两端的端点,然后看看包含这两个端点的序列有多少个,再减去重复的。
 
但是,想法很美好,情况很复杂?,我刚开始就是这么想的。后面发现越来越不对劲,就重新看了一下题目,发现了一些重要的突破口:
1.题目的good序列是“至少”经过一条黑边,注意,这里的用词是用“至少”。
2.原题目的最后还提醒了:总共有n^k个序列,算其中good序列有多少个。
然后我就想到了:既然good序列这么难算,不如算算bad序列?
bad序列规则:第一点和第二点不变,第三点:如果在上述过程中,没有经过一条“黑边”,则这个序列是bad的。也就是说:
如果在上述过程中,经过所有的边都是“红边”(包括没经过边),则这个序列是bad的。
 
这时问题就变得好解决一些了:
只要找出所有的bad序列,通过n^k-bad,就可以间接求出good序列。而bad序列有什么特点?上面文字已经介绍完毕,下面我们观察一下图:
这个图中,由上面的bad文字定义可知:bad序列肯定是在三个分开的集合取:{1,2,4,6},{5},{3,7}。进一步发现:在一个集合的内部是连通的,而且互相到达绝不经过黑边。对于三个集合来说,它们直接被“黑边”分隔开了。也就是说:可以通过去掉“黑边”得到上面的集合:
其实也就是有多少个连通分支,这个用dfs很好解决。
 
得到了这个集合后,就是怎么算的问题了。举个例子:对于这个集合:{1,2,4,6},我们只需要选k次就得到了bad序列的总数,也就是4^k。(不明白的同学可以这样想:要得到长度为k的bad序列,也就是要确定k个位置的值。每个位置都可以选4个元素而保证一定是bad序列,根据组合数学的分步原理,就是4^k个bad序列)。每一个集合我们都算出它们的bad序列的个数,加起来就是总的bad序列个数。
 
 
 
AC代码:
#include <cstdio>
#include <vector>
using namespace std;
const int maxn = 1e5+5;
const int mod = 1e9+7;
int k;
long long n;
vector<int> G[maxn];    //存图
long long cnt[maxn];    //统计每个集合元素个数
int vis[maxn];          //标记/判断i属于哪个集合

void dfs(int u, int num){
    if(vis[u]) return;
    vis[u] = num;    //标记
    for(int i = 0; i < G[u].size(); i++){
        dfs(G[u][i], num);
    }
}

int main(){
    scanf("%lld %d", &n, &k);
    int u, v;
    int is_b;
    for(int i = 0; i < n-1; i++){
        scanf("%d%d%d", &u, &v, &is_b);
        if(!is_b){   //如果不是黑边就加入到图中
            G[u].push_back(v);
            G[v].push_back(u);
        }
    }

    for(int i = 1; i <= n; i++){
        if(!vis[i]) dfs(i, i);
    }

    for(int i = 1; i <= n; i++){
        cnt[vis[i]]++;   //统计每个集合的元素个数
    }

    long long bad = 0;
    for(int i = 1; i <= n; i++){
        long long ans = 1;
        if(cnt[i]){    //如果集合存在
            for(int j = 1; j <= k; j++){  //算cnt[i]的k次方
                ans = ans*cnt[i]%mod;
            }
            bad = (bad+ans)%mod;
        }
    }

    long long all = 1;   //算n的k次方
    for(int i = 1; i <= k; i++){
        all = all*n%mod;
    }

    printf("%lld\n", (all-bad+mod)%mod);  //因为all-bad可能为负数(计算时边取模边算), 所以, 要先加个mod再取模
    return 0;
}

 

 

 
 
 
 

转载于:https://www.cnblogs.com/happy-MEdge/p/10835754.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值