51Nod 1677 treecnt 【树形dp+组合数学+逆元】

51Nod 1677  treecnt

Description:
给定一棵n个节点的树,从1到n标号。选择k个点,你需要选择一些边使得这k个点通过选择的边联通,目标是使得选择的边数最少。

现需要计算对于所有选择k个点的情况最小选择边数的总和为多少。

样例解释:

一共有三种可能:(下列配图蓝色点表示选择的点,红色边表示最优方案中的边)

选择点{1,2}:至少要选择第一条边使得1和2联通。

 

选择点{1,3}:至少要选择第二条边使得1和3联通。

选择点{2,3}:两条边都要选择才能使2和3联通。

Input

第一行两个数n,k(1<=k<=n<=100000)
接下来n-1行,每行两个数x,y描述一条边(1<=x,y<=n)

Output

一个数,答案对1,000,000,007取模。

Input示例

3 2
1 2
1 3

Output示例

4

题解:

一道非常6的好题,思维性很强!!!值得一做!!!首先直接做,要选出 k 个点,然后还要选择一些边使他们构成联通块,复杂度很高。我们换个角度考虑(思维含量贼高):我们考虑一条边对答案的贡献:设一条边连着 x,y 两侧的节点,那么每次选 k 个点,我们有三种取法:                      

(以上图片转载)

1:x 一侧的k个点的联通块2:y 一侧的k个点的联通块3:既包含 x 又包含 y 的k个点的联通块
显然,只有第三种情况这条边对答案有贡献,所以这条边对答案的贡献即为: C(n,k)-C(x,k)-C(y,k);这样一来就简单了:我们通过 dfs dp出每个点的子节点数,这样一来就直接带进公式求值就可以了。(还要注意:这里的组合数要用到阶乘逆元,否则精度会爆)

上代码:

 1 #include<bits/stdc++.h>
 2 #define ll long long
 3 #define mo 1000000007
 4 using namespace std;
 5 const ll N=100005;
 6 const ll M=1e5+5;
 7 vector <ll> mp[100005];
 8 ll ans,n,k;
 9 ll vis[100500];
10 ll step[100005];  //阶乘数组
11 ll unstep[100005];  // 阶乘逆元数组
12
13 //快速幂部分
14 ll qpow(ll x,ll n)
15 {
16     ll res=1;
17     for (; n; n>>=1)
18     {
19         if (n&1) res=res*x%mo;
20         x=x*x%mo;
21     }
22     return res;
23 }
24
25 //阶乘逆元部分
26 void init()
27 {
28     step[1]=1;
29     for (int i=2; i<=M; i++)
30       step[i]=step[i-1]*i%mo;
31     unstep[M]=qpow(step[M],mo-2);
32     for (int i=M-1; i>=0; i--)
33       unstep[i]=unstep[i+1]*(i+1)%mo;
34 }
35
36 //组合数学部分
37 ll C(ll a,ll b)
38 {
39     if (b>a) return 0;
40     if (b==0) return 1;
41     return step[a]*unstep[b]%mo*unstep[a-b]%mo;
42 }
43
44 //dfs部分
45 ll dfs(int x)
46 {
47     vis[x]=1;
48     ll count=1;
49     for (ll int i=0; i<mp[x].size(); i++)
50     {
51         ll u=mp[x][i];
52         if (vis[u]==0)
53         {
54             ll tmp=dfs(u);
55             ans=(ans+(C(n,k)%mo-C(tmp,k)%mo-C(n-tmp,k)%mo)%mo+mo)%mo;
56             count+=tmp;
57         }
58     }
59     return count;
60 }
61
62
63 int main()
64 {
65     while (~scanf("%I64d%I64d",&n,&k))
66     {
67         init();
68         memset(vis,0,sizeof(vis));
69         for (ll i=1; i<=n; i++) mp[i].clear();
70         for (ll i=1; i<=n-1; i++)
71         {
72             ll x,y;
73             scanf("%I64d%I64d",&x,&y);
74             mp[x].push_back(y); mp[y].push_back(x);
75         }
76         ans=0;
77         dfs(1);
78         printf("%I64d\n",(ans+mo)%mo);
79     }
80     return 0;
81 }

加油加油加油!!! fighting fighting fighting !!!

原文地址:https://www.cnblogs.com/Frank-King/p/9304283.html

时间: 2024-10-26 21:47:58

51Nod 1677 treecnt 【树形dp+组合数学+逆元】的相关文章

Codeforces 543D Road Improvement(树形DP+乘法逆元)

题目大概说给一棵树,树的边一开始都是损坏的,要修复一些边,修复完后要满足各个点到根的路径上最多只有一条坏的边,现在以各个点为根分别求出修复边的方案数,其结果模1000000007. 不难联想到这题和HDU2196是一种类型的树形DP,因为它们都要分别求各个点的答案.然后解法也不难想: dp0[u]表示只考虑以u结点为根的子树的方案数 dp1[u]表示u结点往上走,倒过来,以它父亲为根那部分的方案数 有了这两部分的结果,对于各个点u的答案就是dp0[u]*(dp1[u]+1).这两部分求法如下,画

codeforces 500D - New Year Santa Network (树形DP+组合数学)

题目地址:http://codeforces.com/contest/500/problem/D 这题是要先求出每条边出现的次数,然后除以总次数,这样期望就求出来了.先用树形DP求出每个边左右两端总共有多少个点,然后用组合数学公式就可以推出来了. 代码如下: #include <iostream> #include <string.h> #include <math.h> #include <queue> #include <algorithm>

hdu 4661 Message Passing(树形DP&amp;组合数学)

Message Passing Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 1187    Accepted Submission(s): 423 Problem Description There are n people numbered from 1 to n. Each people have a unique mes

BZOJ 4013 HNOI2015 实验比较 树形DP+组合数学

题目大意:给定一张图,每条边有'='和'<'两个属性,每个点入度最多为1,求这张图可以压成多少个用'='和'<'连接的序列 我只贴代码~~ 题解自己搜~~ #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 110 #define MOD 1000000007 using namespace std; struct abcd

51Nod 1677 treecnt

一道比较基础的计数题,还是一个常用的单独计算贡献的例子. 首先看题目和范围,暴力枚举肯定是不可行的,而且\(O(n\ logn)\)的算法貌似很难写. 那我们就来想\(O(n)\)的吧,我们单独考虑每一条边的贡献,我们注意到一个重要的性质: 树上任意两点间的最短路径都是唯一确定的. 这个常识吧,所以我们只需要考虑每一条边两边的点在计算时会经过这条边多少次. 我们枚举每一条边,然后可以这样考虑这一条边: 我们设一边有\(x\)个点,另一边有\(y\)个点,很明显\(x+y=n\) 然后我们考虑有多

[51nod] 1378 夹克老爷的愤怒 #树形DP

1378 夹克老爷的愤怒 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 夹克老爷逢三抽一之后,由于采用了新师爷的策略,乡民们叫苦不堪,开始组织起来暴力抗租. 夹克老爷很愤怒,他决定派家丁常驻村中进行镇压. 诺德县 有N个村庄,编号0 至 N-1,这些村庄之间用N - 1条道路连接起来. 家丁都是经过系统训练的暴力机器,每名家丁可以被派驻在一个村庄,并镇压当前村庄以及距离该村庄不超过K段道路的村庄. 夹克老爷一贯奉行最小成本最大利润的原则,请问要实现对全部村庄

HDU5739 Fantasia 树形dp + 点双缩点

这个题当时打多校的时候有思路,但是代码能力差,没有写出来 事后看zimpha巨巨的题解,看了觉得基本差不多 核心思路:就是找出割点,然后变成森林,然后树形dp就可以搞了 关键就在重新构图上,缩完点以后,一个割点至少在两个点双里面,这个时候 把割点拿出来,分别和点双连边,也就是说,缩完的点双是不包含割点的,这个可以人为搞一下 (像有的点双里面只包含一个桥边,如果把割点拿出来,点双里面没有点了,这个时候把点双的权值积设为1就好) 然后说是树形dp,其实就是逆元搞一搞,这个很简单,树形dp只处理割点的

HDU-2196 Computer (树形DP)

最近在看树形DP,这题应该是树形DP的经典题了,写完以后还是有点感觉的.之后看了discuss可以用树分治来做,以后再试一试. 题目大意 找到带权树上离每个点的最远点.︿( ̄︶ ̄)︿ 题解: 对于每一个点的最远点,就是以这个点为根到所有叶子节点的最长距离.但是如果确定根的话,除了根节点外,只能找到每个节点(度数-1)个子树的最大值,剩下一个子树是该节点当前的父亲节点. 所以当前节点的最远点在当前节点子树的所有叶子节点以及父亲节点的最远点上(当父亲节点的最远点不在当前节点的子树上时), 如果父亲节

UVA-01220 Party at Hali-Bula (树形DP+map)

题目链接:https://vjudge.net/problem/UVA-1220 思路: 树形DP模板题,求最大人数很简单,难点在于如何判断最大人数的名单是否有不同的情况: 解决方法是用一个数组f[manx][2]记录该节点是否出场的情况,为真时代表有多种情况; 具体讨论: 当父节点的值加上某个子节点的值时,他的f的情况也和该子节点一样: 当某个节点dp(i, 0) == dp(i, 1), 则该节点以及它的父节点也一定有多种情况(父节点必定取其中之一). Code: 1 #include<bi