C. Edgy Trees Codeforces Round #548 (Div. 2) 【连通块】

题面:

传送门

题目描述:

给出有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序列有什么特点?上面文字已经介绍完毕,下面我们观察一下图:

img

这个图中,由上面的bad文字定义可知:bad序列肯定是在三个分开的集合取:{1,2,4,6},{5},{3,7}。进一步发现:在一个集合的内部是连通的,而且互相到达绝不经过黑边。对于三个集合来说,它们直接被“黑边”分隔开了。也就是说:可以通过去掉“黑边”得到上面的集合:

img

其实也就是有多少个连通分支,这个用dfs很好解决。

得到了这个集合后,就是怎么算的问题了。举个例子:对于这个集合:{1,2,4,6},我们只需要选k次就得到了bad序列的总数,也就是4k。(不明白的同学可以这样想:要得到长度为k的bad序列,也就是要确定k个位置的值。每个位置都可以选4个元素而保证一定是bad序列,根据组合数学的分步原理,就是4k个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;
}

题目分析二

这题刚开始没读懂题意,后来明白了,原来就是一个数连通块里点数的问题。首先在建图的时候,只考虑红色路径上的点。为什么呢,因为为了不走红色的快,那么我们可以反着想只走红色的路径,这样把所有的可能数再减去只走红色路径的数就是最终的答案了。这里要注意的是,如果连通块里只有一个点,那么就是K个点都是这个点的情况,根据题意是不满足的,也要减去。

AC代码二

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 15;
vector<int> Graph[MAXN];
bool visited[MAXN];
int T, N, K;

LL Pow(LL a, LL b)
{
    LL ans = 1;
    while(b)
    {
        if(b&1)
        {
            ans = ans * a % MOD;
        }
        a = a * a % MOD;
        b >>= 1;
    }
    return ans;
}

void DFS(int v)
{
    if(visited[v])
        return;
    visited[v] = 1;
    T++;
    for(auto &itr:Graph[v])
    {
        DFS(itr);
    }
}



int main()
{
    scanf("%d %d", &N, &K);
    memset(visited, 0, sizeof(visited));
    for(int i = 1; i < N; i++)
    {
        int x, y, c;
        scanf("%d %d %d", &x, &y, &c);
        if(c == 0)
        {

            Graph[x].push_back(y);
            Graph[y].push_back(x);
        }
    }
    LL Ans = 0;
    for(int i = 1; i <= N; i++)
    {
        if(visited[i])
            continue;
        T = 0;
        DFS(i);
        Ans = (Ans + Pow(T, K)) % MOD;
    }
    Ans = (Pow(N, K) - Ans + MOD) % MOD;
    printf("%I64d\n", Ans);
    return 0;
}

题目分析三

题意是给了一棵树,n个点,m条边。让从中选k个点,使得从a1到a2,a2到a3,ak-1到ak的路径中至少经过一条黑色的边,问这样的集合有多少个

​ 思路就是求他们的组合数,所有的可能就是nk,假如说像第一个样例那样只有黑色边存在,那么不可行的情况就是下面解释的那样,所以答案就是nk - n,如果存在红色边的情况,比如说第二个样例,因为2到3连的是红边,那么对于只有这两个点的集合来说,他们的所有情况为2k,所以答案就是nk - 2^k - 1(这个1是1 1 1的情况)。那么我们反过来看题中的那个图,连的有红色边的点有6个,那么我们如果让n^k - 6k肯定是不对的,因为有些点是可以经过黑边到达另一个点的,比如从1到7,那么这样的集合是不应该减的,所以我们要减的应该是只被红边连接的点,而且必须要连通,也就是减去4k再减去2^k,然后再减去那些剩下的一个集合中只有1个数的情况。

​ 所以这道题的正解应该是我们将那些连了红边的点建边,然后对于每一个点跑一边dfs,目的是为了找出与当前这个点相连的点有多少个(就是找用红边相连的联通块),然后减去这个cnt^k就好了。感觉讲的不太好理解,自己看着题上的图想一下就好了。

AC代码三

#include <bits/stdc++.h>
#define ll long long
#define maxn 200005
const ll mod = 1e9 + 7;
using namespace std;
struct Node{
	int to,next,w;
}Edge[maxn];
int head[maxn],num;
ll n,k,cnt;
bool vis[maxn];
map<int,int> ma1,ma2;

ll ppow(ll a, ll b){
	ll sum = 1;
	a %= mod;
	while(b > 0){
		if(b % 2 == 1) sum = (sum * a) % mod;
		b /= 2;
		a = (a * a) % mod;
	}
	return sum % mod;
}

void init(){
	for(int i=0;i<=n;i++) head[i] = -1;
	num = 0;
}

void add(int u,int v,int w){
	Edge[num].to = v;
	Edge[num].w = w;
	Edge[num].next = head[u];
	head[u] = num ++;
}

void dfs(int x){
	cnt ++;
	for(int i=head[x];i!=-1;i=Edge[i].next){
		int to = Edge[i].to;
		if(!vis[to]){
			vis[to] = true;
			dfs(to);
		}
	}
}

int main()
{
  scanf("%lld%lld",&n,&k);
	init();
  for(int i=1;i<n;i++){
    int u, v, w;
    scanf("%d%d%d",&u,&v,&w);
		if(w == 0){
			add(u, v, w);
			add(v, u, w);
		}
  }
  ll cnt2 = 0;
  ll ans = ppow(n, k);
	memset(vis,false,sizeof(vis));
	for(int i=1;i<=n;i++){
		if(vis[i] == false){
			vis[i] = true;
			cnt = 0;
			dfs(i);
			ans = (ans - ppow(cnt, k) + mod) % mod;
		}
	}
  printf("%lld\n", ans);
  return 0;
}

C. Edgy Trees Codeforces Round #548 (Div. 2) 【连通块】

Codeforces Round #548 C. Edgy Trees

geeksforgeeks

Codeforces Round #548 (Div. 2)C. Edgy Trees(dfs,思维)

Codeforces Round #548 (Div. 2) C. Edgy Trees(并查集+快速幂)

posted @ 2019-08-26 09:43  云计算-李耀  阅读(156)  评论(0编辑  收藏  举报