#2018-2019 ACM-ICPC, Asia Xuzhou Regional Contest G (LCA + 树上差分 + 组合数)

G. Rikka with Intersections of Paths

time limit per test

6.0 s

memory limit per test

1024 MB

input

standard input

output

standard output

Rikka has a tree TT with nn vertices numbered from 11 to nn .

Meanwhile, Rikka has marked mm simple paths in TT , the ii -th of which is between the vertices xixi and yiyi , where some of them could be the same path.

Now, Rikka wants to know in how many different strategies she can select kk paths from the marked paths such that those selected paths share at least one common vertex.

Input

The input contains several test cases, and the first line contains a single integer TT (1≤T≤2001≤T≤200 ), the number of test cases.

For each test case, the first line contains three integers nn (1≤n≤3×1051≤n≤3×105 ), the size of the tree TT , mm (2≤m≤3×1052≤m≤3×105 ), the number of marked paths, and kk (2≤k≤m2≤k≤m ).

The following (n−1)(n−1) lines describe the tree TT . Each of them contains two integers uu and vv (1≤u,v≤n1≤u,v≤n , u≠vu≠v ), representing an edge between the vertices uu and vv .

The following mm lines describe all marked simple paths in the tree. The ii -th of them contains two integers xixi and yiyi (1≤xi,yi≤n1≤xi,yi≤n ).

The input guarantees that the sum of nn and the sum of mm in all test cases are at most 2×1062×106 respectively.

Output

For each test case, output a single line with a single integer, the number of different strategies meeting the requirement modulo (109+7)(109+7) .

Example

Input

Copy

1
3 6 2
1 2
1 3
1 1
2 2
3 3
1 2
1 3
2 3

Output

Copy

10

题目大意 : 输入一棵树, M次操作, 每次在点 U 和 V之间路径的所有点之间连一条边, 从添加的路径当中选择K个路径, 问有多少种方案可以使选中的K个路径的公共点数 ≥ 1

思路 :由于树上两点之间的路径只有一个, 所以可以利用树上差分表示出每个点被经过了多少次, 具体实现为  sum[U]++, sum[V]++, sum[LCA(U, V)]--, sum[fa[LCA(U, V)]]--, 这个不难理解

而答案就是每个点的贡献之和, 贡献的求法为 :C(num[x], k) - C(num[x] - lca[x], k), 其中lca【x】表示的是该点作为最近共同祖先所经过的边的数目, 在操作的过程就就可以求得, 实际上就是该点贡献减去父亲的贡献

Accepted code

#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;

#define sc scanf
#define ls rt << 1
#define rs ls | 1
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define MEM(x, b) memset(x, b, sizeof(x))
#define lowbit(x) ((x) & -(x))
#define P2(x) ((x) * (x))

typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 3e5 + 100;
const int INF = 0x3f3f3f3f;

struct Edge
{
	int v, next; 
}e[MAXN << 1];
int head[MAXN], n, m, k, cnt, T;
int p[MAXN][25], lca[MAXN];
int dep[MAXN], c[MAXN];
ll fac[MAXN], inv[MAXN], pos; 
ll pow_mod(ll a, ll b) {     // 快速幂取模
	ll res = 1;
	while (b) {
		if (b & 1) res = (res % MOD) * (a % MOD) % MOD;
		a = (a % MOD) * (a % MOD) % MOD;
		b >>= 1;
	}
	return res % MOD;
}
void init() {
	MEM(head, -1); MEM(p, 0); MEM(dep, 0); MEM(c, 0); MEM(lca, 0);
	cnt = pos = 0;
}
ll Cc(ll a, ll b) {    // 组合数
	if (b == 0 || a < b) return 0;
	return fac[a] * inv[b] % MOD * inv[a - b] % MOD;
}
void add(int from, int to) {
	e[++cnt].v = to;
	e[cnt].next = head[from];
	head[from] = cnt;
}
void dfs(int x, int fa) {   // 找出倍增关系和每个点的深度
	p[x][0] = fa, dep[x] = dep[fa] + 1;
	for (int i = 1; (1 << i) <= dep[x]; i++)
		p[x][i] = p[p[x][i - 1]][i - 1];
	for (int i = head[x]; i != -1; i = e[i].next) {
		int vi = e[i].v;
		if (vi == fa) continue;
		dfs(vi, x);
	}
}
int LCA(int x, int y) {  // 求最近共同祖先
	if (dep[x] > dep[y]) swap(x, y);
	for (int i = 20; i >= 0; i--) {
		if (dep[y] - (1 << i) >= dep[x])
			y = p[y][i];
	}
	if (x == y) return x;
	for (int i = 20; i >= 0; i--) {
		if (p[x][i] == p[y][i]) continue;
		x = p[x][i], y = p[y][i];
	}
	return p[x][0];
}
void DFS(int x, int fa) {   // 树上差分回溯, 并记录答案, 答案当中有减号,先 + MOD再取模
	for (int i = head[x]; i != -1; i = e[i].next) {
		int vi = e[i].v;
		if (vi == fa) continue;
		DFS(vi, x); c[x] += c[vi];
	}
	pos = ((pos % MOD) + ((Cc(c[x], k) % MOD) - (Cc(c[x] - lca[x], k) % MOD)) + MOD) % MOD;
}

int main()
{
	fac[0] = inv[0] = 1;
	for (int i = 1; i < MAXN; i++) {
		fac[i] = fac[i - 1] * i % MOD;
		inv[i] = pow_mod(fac[i], MOD - 2);
	}
	cin >> T;
	while (T--) {
		sc("%d %d %d", &n, &m, &k); init();
		for (int i = 1; i < n; i++) {
			int ui, vi;
			sc("%d %d", &ui, &vi);
			add(ui, vi); add(vi, ui);
		}
		dfs(1, 0);
		for (int i = 0; i < m; i++) {
			int ui, vi;
			sc("%d %d", &ui, &vi);
			int ans = LCA(ui, vi);
			c[ui]++, c[vi]++, c[ans]--, c[p[ans][0]]--;
			lca[ans]++;
		}
		DFS(1, 0);
		printf("%lld\n", pos % MOD);
	}
	return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值