Codeforces Round #588 (Div. 2)E. Kamil and Making a Stream(树上倍增/dfs暴力)

E. Kamil and Making a Stream

题意:

输入 n ( 1 e 5 ) n(1e5) n(1e5),表示树有 n n n个节点;
输入 x 1 , x 2 , … , x n ( 1 e 12 ) x_1,x_2,\dots,x_n(1e12) x1,x2,,xn(1e12)表示树上节点的权值;
接下来 n − 1 n-1 n1行每行输入 u , v u,v u,v表示树边。
问树中所有存在父子关系的点对,求两点路径权值的 g c d gcd gcd,求出所有点对 g c d gcd gcd和。

题解1(树上倍增):

1、首先预处理出倍增数组:
f [ u ] [ i ] f[u][i] f[u][i]表示从 u u u开始向上 2 i 2^i 2i的节点是什么。
g g [ u ] [ i ] gg[u][i] gg[u][i]表示从 u u u开始向上 2 i 2^i 2i的节点及经过节点权值最大公约数。
2、然后对每个点算它作为子孙的贡献:
那么就应该找的祖先节点。
向上倍增的找相同 g c d gcd gcd的一段,然后继续向上,直到根节点。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
#define ll long long
const int mod=1e9+7;
ll x[N],n;
vector<int>g[N];
inline ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll f[N][21],gg[N][21],dep[N];
void dfs(int u,int fa){
    gg[u][0]=gcd(x[u],x[fa]);f[u][0]=fa;dep[u]=dep[fa]+1;
    for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1],gg[u][i]=gcd(gg[u][i-1],gg[f[u][i-1]][i-1]);
    for(auto v:g[u]){
        if(v==fa)continue;
        dfs(v,u);
    }
}
ll ans;
int main(){
  //  freopen("tt.in","r",stdin),freopen("tt.out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>x[i];
    for(int i=1,u,v;i<n;i++)cin>>u>>v,g[u].push_back(v),g[v].push_back(u);
    dfs(1,0);
    for(int i=1;i<=n;i++){
        ll gys=x[i],l=i,r=i;
        while(r){
          for(int j=20;j>=0;j--)if(gcd(gys,gg[l][j])==gys&&f[l][j])l=f[l][j];
          ans=(ans+1ll*gys*(dep[r]-dep[l]+1))%mod;
          l=r=f[l][0],gys=gcd(gys,x[r]);
        }
    }
    cout<<ans<<endl;
    return 0;
}

题解2(无脑dfs暴力)

虽说是无脑暴力,但它跑起来比树上倍增要快。一开始我从底向上合并 g c d gcd gcd,直到根节点,这样会超时;后来灵机一动,直接从顶向下传递 g c d gcd gcd就好了,这样就变成一条链的 g c d gcd gcd,显然不会TLE。
每个节点维护的是从根节点到该节点的后缀 g c d gcd gcd,可以用 m a p , v e c t o r map,vector map,vector等维护。

代码:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
#define mp map<ll,int>
using namespace std;
const int mod=1e9+7;
const int N=1e5+9;
ll x[N],n;
mp y[N];
vector<int>g[N];
ll ans=0;
inline ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
void dfs(int u,int fa){
    y[u][x[u]]++;
    for(auto i:y[fa])y[u][gcd(x[u],i.first)]+=i.second;
    for(auto v:g[u]){
        if(v==fa)continue;
        dfs(v,u);
    }
    for(auto j:y[u])ans=(ans+j.first%mod*j.second)%mod;
    y[u].clear();
}
int main(){
   // freopen("tt.in","r",stdin),freopen("tt.out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>x[i];
    for(int i=1,u,v;i<n;i++)cin>>u>>v,g[u].push_back(v),g[v].push_back(u);
    dfs(1,0);
 //   for(int i=1;i<=n;i++){
  //      for(auto j:y[i])cout<<j.first<<" "<<j.second<<endl;
   // }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值