2018徐州 - G - Rikka with Intersections of Paths (树上差分 + 组合数)
题目链接:http://codeforces.com/gym/102012/problem/G
题意
给你一颗有N个结点的树,再给你M条路径,和一个K,代表的是从M条路中任选K条路径,他们再一个点上都共有交点的组合数是多少?
数据范围: 1 ≤ N , M ≤ 3 ∗ 1 0 5 ; 1 ≤ N ≤ 3 ∗ 1 0 5 1 \le N ,M \le 3*10^5;1 \le N \le 3*10^5 1≤N,M≤3∗105;1≤N≤3∗105
思路
一开始的想法是记录每一个点的经过的路径数 n n n ,计算 C n k C_n^k Cnk,最终累加起来,但是这样显然会有大量的重复计算。那么如何不重复呢,我在比赛中想到的做法是既然 那几条路径出现了重复计算,那么意味着我从当前点向四周点转移的时候,如果增加了一些通过的路径数,那么我就通过现在的计算 C n 2 k C_{n2}^k Cn2k 减去所有子树中已经计算过的方案 ∑ C n 1 k \sum C_{n1}^k ∑Cn1k ,那么就是新产生的方案数。
那么如何求每个点上经过路径的点数呢?
我所采用的是开两个数组, a d d [ u ] + + , a d d [ v ] + + , a d d [ l c a ( u , v ) ] − − , s u b [ l c a ( u , v ) ] + + add[u]++,add[v]++,add[lca(u,v)]--,sub[lca(u,v)]++ add[u]++,add[v]++,add[lca(u,v)]−−,sub[lca(u,v)]++ ,我们先进行的是add操作,整个点算完后才进行sub操作。
题解做法:
看了题解,才发现原来树上两个路径有这个性质
一个树上任意两条路径如果有交点的话,那么这些交点中肯定有一个为两条路径中的一条路径两端点的lca。
那么我们可也可以利用这个性质,假设通过当前点的路径数为 N N N,以当前点为LCA的点为 M M M,那么当前点的贡献就是 C N K − C N − M K C_{N}^{K}-C_{N-M}^K CNK−CN−MK ,这么算是一定不会出现重复的。
代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define debug(x) cerr<<#x<<":"<<x<<endl
#define pb push_back
void test(){cerr<<"\n";}
template<typename T,typename... Args>void test(T x,Args... args){cerr<<x<<" ";test(args...);}
typedef long long ll;
typedef pair<int,int> pi;
const int MAXN = (int)3e5+7;
inline int rd() { int c = 0, f = 1;char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1;ch = getchar();}
while (ch >= '0' && ch <= '9') {c = c * 10 + ch - '0';ch = getchar();}
return c * f;
}
const ll MOD = (ll)1e9+7;
ll fpow(ll a,ll x) {
ll res = 1;
while (x) {
if (x&1) res = res*a%MOD;
x >>= 1;
a = a*a%MOD;
}
return res;
}
inline ll Inv(ll a) {
return fpow(a,MOD-2);
}
ll fac[MAXN];
inline ll C(ll b,ll a) {
if (b>a) return 0LL;
ll res = fac[a]*Inv(fac[b])%MOD*Inv(fac[a-b])%MOD;
return res;
}
void F_init() {
fac[0] = 1;
rep(i,1,3e5) fac[i] = fac[i-1]*i%MOD;
}
int n;
vector<int> G[MAXN];
int father[MAXN];
int dep[MAXN];
inline void dfs(int u, int fa, int deep){
father[u]= fa;
dep[u]= deep;
for(int i = 0; i < G[u].size(); ++i){
int to = G[u][i];
if(to == fa) continue;
dfs(to, u, deep+1);
}
}
int p[MAXN][30];
void Init_LCA(){
for(int j=0; (1<<j)<=n; ++j)
for(int i=1; i<=n; ++i)
p[i][j]= 0;
for(int i=1; i<=n; ++i) p[i][0]= father[i];
for(int j=1; (1<<j)<=n; ++j)
for(int i=1; i<=n; ++i)
if( p[i][j-1] != 0 )
p[i][j]= p[ p[i][j-1] ][ j-1 ];
}
inline int LCA(int x, int y){
if( dep[x] < dep[y] ) swap(x, y);
int i,lg;
for(lg=0; (1<<lg)<=dep[x]; ++lg);
--lg;
for(i=lg; i>=0; --i)
if( dep[x] - (1<<i) >= dep[y] )
x= p[x][i];
if( x==y ) return x;
for(i=lg; i>=0; --i)
if( p[x][i] != 0 && p[x][i] != p[y][i] )
x= p[x][i], y= p[y][i];
return father[x];
}
int add[MAXN],sub[MAXN];
ll ans;
inline int DFS(int u,int fa,int k) {
ll num = add[u],laNum = 0;
ll sum = 0;
rep(i,0,G[u].size()-1) {
int v = G[u][i];
if (v == fa) continue;
laNum = DFS(v,u,k);
sum += C(k,laNum);
num += laNum;
}
ll tmp = C(k,num)-sum;
ans += C(k,num)-sum+MOD;
ans %= MOD;
num -= sub[u];
return num;
}
void init(int N) {
rep(i,1,N) G[i].clear();
rep(i,1,N) add[i] = sub[i] = 0;
ans = 0;
n = N;
}
int main()
{
int T;
scanf("%d",&T);
F_init();
while (T --) {
int N,M,K;
scanf("%d %d %d",&N,&M,&K);
init(N);
rep(i,1,N-1) {
int u,v;
scanf("%d %d",&u,&v);
G[u].pb(v);
G[v].pb(u);
}
dfs(1, 0, 0);
Init_LCA();
rep(i,1,M) {
int u,v;
scanf("%d %d",&u,&v);
int lc = LCA(u,v);
add[u] ++;
add[v] ++;
sub[lc] ++;
add[lc] --;
}
DFS(1,0,K);
printf("%lld\n",ans);
}
}