牛客练习 争分夺秒(lca)

题目描述 

七夕节到了,牛牛要去找牛妹约会了。

牛牛知道自己如果空手去约会,绝对会遭到牛妹的无情殴打。所以他决定打电话找跑腿,让跑腿去买花送到牛妹家楼下,牛牛要在牛妹下楼前到牛妹家楼下并且拿到花才不会遭到牛妹的无情殴打。

现在把整个城市看作一棵n个结点的树,牛妹在点1,牛牛家在点a,花店在点b,跑腿的起始点在c,牛牛在出发前会给牛妹打电话,问牛妹还有多久下楼。

为了让自己能够安然的度过今天,牛牛模拟了q个场景,想要计算一下自己能够不遭牛妹殴打的概率。

如果牛牛刚到牛妹家楼下,或者刚拿到花,这个时候牛妹刚好下楼,牛妹仍然会殴打牛牛。

输入描述:

第一行有两个正整数n(n<=200000),表示有n个结点

接下来n-1行每行有两个正整数u,v(u,v<=n)表示u,v两个结点有一条边

第n+1行有一个q(q<=200000),表示牛牛会给牛妹打q次电话

接下来q行,每行有四个正整数a,b,c,t(a,b,c<=n,t<=200000)a,b,c如题意描述,t为牛妹下楼的时间

输出描述:

输出p/q mod 1e9+7,表示牛牛不会被牛妹殴打的概率。

思路

大意是求a到1(根节点)的距离与b、c之间距离+b到1(根节点)的距离的较大值与t比较,比t小则即为成功的概率。

节点到根节点的距离就是节点深度,而b、c之间的距离等于sum[b]+sum[c]-2*sum[l],l是两节点最近公共祖先。

问题是裸的lca(最近公共祖先),考虑规模n<=2e6,直接使用暴力o(n)可以通过,根据树的深度,先将两个结点

化在同一层,再从小到大同时一层一层往上找,两个点相遇就是答案。取模过程要用到费马小定理

当m是质数时,有费马小定理 a^m-1 ≡ 1(mod m)。(m/n)%p=(m*n^(p-2))%p;

#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
typedef long long int ll;
int dsu[maxn],dep[maxn];
int n,u,e,q,a,b,c,t;
vector<int>v[maxn];
ll mod=1e9+7;
void dfs(int node,int fa,int depth){
    dsu[node]=fa;///记录父亲
    dep[node]=depth;///记录深度
    for(int i=0;i<v[node].size();i++){
        if(fa!=v[node][i])dfs(v[node][i],node,depth+1);///防止父子来回访问
    }
}
int lca(int x,int y){
    while(dep[x]>dep[y])x=dsu[x];
    while(dep[y]>dep[x])y=dsu[y];///将两点移动到同一深度
    while(x!=y){///从下往上找祖先
        x=dsu[x];
        y=dsu[y];
    }
    return x;
}
ll qpow(ll a,ll b,ll c){
    ll ans=1;
    while(b){
        if(b&1)ans*=a;
        a*=a;
        a%=c;
        ans%=c;
        b>>=1;
    }
    return ans%c;
}
int main(){
   cin>>n;
   for(int i=0;i<n-1;i++){
        cin>>u>>e;
        v[u].push_back(e);
        v[e].push_back(u);
   }
   dfs(1,-1,0);
   cin>>q;
   ll cnt=0;
   for(int i=0;i<q;i++){
        cin>>a>>b>>c>>t;
        int l=lca(b,c);
        int dis=dep[b]+dep[c]-2*dep[l]+dep[b];
        int maxx=max(dep[a],dis);
        if(maxx<t){
            cnt++;
        }
   }
   cout<<(cnt*(qpow(q,mod-2,mod)))%mod<<endl;
   return 0;
}
///当m是质数时,有费马小定理 a^m-1 ≡ 1(mod m)。
///(m/n)%p=(m*n^(p-2))%p;

考虑优化,使用倍增算法,o(logn)

#include<bits/stdc++.h>
using namespace std;
const int maxn=200005;
typedef long long int ll;
int dsu[maxn],dep[maxn];
int f[maxn][35];
int n,u,e,q,a,b,c,t;
vector<int>v[maxn];
ll mod=1e9+7;
void dfs(int node,int fa,int depth){
    f[node][0]=fa;///0是2^0次方即当前点的父亲节点
    dep[node]=depth;
    for(int i=1;i<=32;i++){///递推关系2^i=2^i-1+2^i-1;
        f[node][i]=f[f[node][i-1]][i-1];
    }
    for(int i=0;i<v[node].size();i++){
        if(fa!=v[node][i])dfs(v[node][i],node,depth+1);///fa!=v[node][i],防止父子节点重复遍历。
    }
}
int lca(int x,int y){
    if(dep[x]>dep[y])swap(x,y);///默认x在上面
    for(int i=32;i>=0;i--){
        if(dep[f[y][i]]>=dep[x]){
            y=f[y][i];
        }
    }///将x、y转为同一层
    if(x!=y){
        for(int i=32;i>=0;i--){///从追溯到最早的祖先开始
            if(f[x][i]!=f[y][i]){///不能相遇
                x=f[x][i];
                y=f[y][i];
            }
        }
        return f[x][0];///相遇后的上一个点就是最近公共祖先
    }
    else return x;///如果相等直接跳出
}
ll qpow(ll a,ll b,ll c){
    ll ans=1;
    while(b){
        if(b&1)ans*=a;
        a*=a;
        a%=c;
        ans%=c;
        b>>=1;
    }
    return ans%c;
}
int main(){
   cin>>n;
   for(int i=0;i<n-1;i++){
        cin>>u>>e;
        v[u].push_back(e);
        v[e].push_back(u);
   }
   dfs(1,-1,0);///根节点、父亲节点、深度
   cin>>q;
   ll cnt=0;
   for(int i=0;i<q;i++){
        cin>>a>>b>>c>>t;
        int l=lca(b,c);
        int dis=dep[b]+dep[c]-2*dep[l]+dep[b];///跑腿的花费距离
        int maxx=max(dep[a],dis);
        if(maxx<t){
            cnt++;
        }
   }
   cout<<(cnt*(qpow(q,mod-2,mod)))%mod<<endl;
   return 0;
}
///当m是质数时,有费马小定理 a^m-1 ≡ 1(mod m)。
///(m/n)%p=(m*n^(p-2))%p;
原文地址:https://www.cnblogs.com/mohari/p/13578417.html