题解 2018徐州 - G - Rikka with Intersections of Paths (树上差分 + 组合数)

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 1N,M3105;1N3105


思路

一开始的想法是记录每一个点的经过的路径数 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 CNKCNMK ,这么算是一定不会出现重复的。


代码

#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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Best KeyBoard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值