题面传送门

题意:

有一棵 \(n\) 个节点的树,点上有点权 \(a_i\),\(q\) 组询问,每次询问给出 \(u,v,w\),要求:

  • \(\prod\limits_{x\in P(u,v)}\operatorname{gcd}(a_x,w)\)

其中 \(P(u,v)\) 为 \(u\) 到 \(v\) 的简单路径上经过的点的集合。

\(n,q \leq 10^5,a_i \leq 10^7\)

显然本题需要分解质因数,不妨设 \(w\) 分解质因数得到 \(p_1^{f_1}\times p_2^{f_2}\times\dots\times p_k^{f_k}\)。

我们对 \(w\) 的每个质因子 \(p_i\) 计算对应的贡献,对于链上的每个数 \(a_x\),假设它质因数 \(p_i\) 的次数为 \(g_i\),那么它的贡献就是 \(p_i^{\min(f_i,g_i)}\)。

于是我们可以暴力枚举 \(p_i\) 的次数 \(g_i\),假设 \(u\to v\) 的路径上有 \(cnt\) 个 \(a_x\) 中 \(p_i\) 的次数恰好为 \(g_i\),这部分的答案应为 \((p_i^{\min(f_i,g_i)})^{cnt}\)。

接下来就是怎样求这个 \(cnt\),对 \(a_i\) 分解质因数得到 \(a_i=q_1^{h_1}\times q_2^{h_2}\times\dots\times q_l^{h_l}\),将每个 \((q_i,h_i)\) 看作一种“颜色”,每个点上最多有 \(7\) 种颜色。那么题目就转化为求 \(u\) 到 \(v\) 路径上有某种“颜色”的点的个数。

考虑用树剖把原区间拆成 \(\log n\) 个小区间,然后用主席树维护区间内值为 \(x\) 的数的个数就可以求出 \(cnt\) 的值。

算算复杂度,暴力枚举 \(g_i\) 的常数最大可以达到为 \(\log_2(10^7)+\log_3(10^7)+\log_5(10^7)+\log_7(10^7)+\log_{11}(10^7)+\log_{13}(10^7)\) ,大约 \(60\),树剖+主席树各有一个 \(\log\),所以总复杂度是 \(60q\log^2n\),太逊了。

发现本题并没有强制在线,所以可以离线地去完成路径上数颜色这一工作。这玩意儿可以利用树上差分拆为 \(u,v,\operatorname{lca}(u,v),fa_{\operatorname{lca}(u,v)}\) 四个部分,最终的 \(cnt=cnt_u+cnt_v-cnt_{\operatorname{lca}(u,v)}-cnt_{fa_{\operatorname{lca}(u,v)}}\)。

枚举每一种颜色 \(c\),然后依次处理查询颜色为 \(c\) 的询问。假设某个点 \(x\) 拥有颜色 \(c\),那么它会对 \(x\) 的子树内的询问产生 \(1\) 的贡献,利用 DFS 序将子树变为 DFS 序上的一个区间,于是本题就变为区间加、单点查询的问题,一个线段树就可以搞定了。

复杂度可以少掉一个 \(\log\), \(60q\log n\) 大约为 \(1.5\times 10^8\)。

想到这里,我就没有再想下去。当我美滋滋地交上去,拥有十足的信心 A 掉这道题的时候……

TLE on test 9?

虽然理论复杂度是 ok 的,但实测常数很大。首先你要将每个找颜色的询问拆成四个询问,就已经有了 \(4\) 的常数,再加上快速幂、取模的常数,将近有 \(10^9\)。对这种情况,哪怕再神的评测机也没法儿给你跑过啊……

顺便附上 T 掉了的代码:

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
typedef pair<int,int> pii;
typedef long long ll;
const int MOD=1e9+7;
const int MAXN=1e5+5;
const int MAXC=MAXN*10;
const int MAXQ=MAXN*150;
int n,q,qn,a[MAXN],tnum=0,num=0;
vector<pii> fac[MAXN];
pii key[MAXC],hs[MAXC];
map<pii,bool> vis;
int hd[MAXN],nxt[MAXN<<1],to[MAXN<<1],ec=0;
void adde(int u,int v){
to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;
}
int fa[MAXN][22],dfn[MAXN],ed[MAXN],tim=0,dep[MAXN];
void dfs(int x,int f){
fa[x][0]=f;dfn[x]=++tim;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dep[y]=dep[x]+1;dfs(y,x);
} ed[x]=tim;
}
int getlca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;~i;i--) if(dep[x]-(1<<i)>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=20;~i;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
struct Query{
int f,id,x,v;//ans[id]*=v^{num}
Query(){/*var nothing*/}
Query(int _f,int _id,int _x,int _v){
f=_f;id=_id;x=_x;v=_v;
}
friend bool operator <(Query lhs,Query rhs){
return lhs.f<rhs.f;
}
} qu[MAXQ];
vector<int> add[MAXC];
int qpow(int x,int e){
int ret=1;
while(e){
if(e&1) ret=1ll*ret*x%MOD;
x=1ll*x*x%MOD;e>>=1;
} return ret;
}
int ans[MAXN];
struct node{
int l,r,val,lz;
} s[MAXN<<2];
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r) return;
int mid=(l+r)>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void pushdown(int k){
if(s[k].lz){
s[k<<1].val+=s[k].lz*(s[k<<1].r-s[k<<1].l+1);
s[k<<1].lz+=s[k].lz;
s[k<<1|1].val+=s[k].lz*(s[k<<1|1].r-s[k<<1|1].l+1);
s[k<<1|1].lz+=s[k].lz;
s[k].lz=0;
}
}
void modify(int k,int l,int r,int x){
if(l<=s[k].l&&s[k].r<=r){
s[k].val+=(s[k].r-s[k].l+1)*x;s[k].lz+=x;return;
} pushdown(k);int mid=(s[k].l+s[k].r)>>1;
if(r<=mid) modify(k<<1,l,r,x);
else if(l>mid) modify(k<<1|1,l,r,x);
else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
s[k].val=s[k<<1].val+s[k<<1|1].val;
}
int query(int k,int l,int r){
if(l<=s[k].l&&s[k].r<=r) return s[k].val;
pushdown(k);int mid=(s[k].l+s[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return query(k<<1,l,mid)+query(k<<1|1,mid+1,r);
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);adde(u,v);adde(v,u);
}
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
int tmp=a[i];
for(int j=2;j*j<=tmp;j++){
if(tmp%j==0){
int cnt=0;
while(tmp%j==0) tmp/=j,cnt++;
fac[i].pb(mp(j,cnt));
// printf("{%d,%d} ",j,cnt);
key[++tnum]=mp(j,cnt);
}
}
if(tmp>1){
fac[i].pb(mp(tmp,1));
// printf("{%d,%d} ",tmp,1);
key[++tnum]=mp(tmp,1);
}
// printf("\n");
}
sort(key+1,key+tnum+1);
for(int i=1;i<=tnum;i++) if(key[i]!=key[i-1])
hs[++num]=key[i],vis[key[i]]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<fac[i].size();j++){
int id=lower_bound(hs+1,hs+num+1,fac[i][j])-hs;
add[id].push_back(i);//printf("%d %d\n",id,i);
}
}
dfs(1,0);
for(int i=1;i<=20;i++) for(int j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
scanf("%d",&q);
for(int i=1;i<=q;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
int tmp=w;vector<pii> ff;ans[i]=1;
for(int j=2;j*j<=tmp;j++){
if(tmp%j==0){
int cnt=0;
while(tmp%j==0) tmp/=j,cnt++;
ff.pb(mp(j,cnt));
}
}
if(tmp>1) ff.pb(mp(tmp,1));
for(int j=0;j<ff.size();j++){
int val=1,pw=qpow(ff[j].fi,ff[j].se);
for(int k=1;val<=1e7;k++){
val*=ff[j].fi;
if(!vis[mp(ff[j].fi,k)]) continue;
int f=lower_bound(hs+1,hs+num+1,mp(ff[j].fi,k))-hs;
qu[++qn]=Query(f,i,u,min(val,pw));
qu[++qn]=Query(f,i,v,min(val,pw));
int lca=getlca(u,v);
qu[++qn]=Query(f,i,lca,qpow(min(val,pw),MOD-2));
if(lca!=1) qu[++qn]=Query(f,i,fa[lca][0],qpow(min(val,pw),MOD-2));
}
}
}
build(1,1,n);sort(qu+1,qu+qn+1);int j=1;
for(int i=1;i<=num;i++){
ffe(it,add[i]) modify(1,dfn[*it],ed[*it],1);
while(j<=qn&&qu[j].f==i){
int cnt=query(1,dfn[qu[j].x],dfn[qu[j].x]);
// printf("%d %d\n",j,cnt);
ans[qu[j].id]=1ll*ans[qu[j].id]*qpow(qu[j].v,cnt)%MOD;j++;
}
ffe(it,add[i]) modify(1,dfn[*it],ed[*it],-1);
}
for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
return 0;
}

wtcl 啊,下面开始讲正解,其实稍微变通一下就行了。

正解还是基于之前那个“数颜色”的做法,不过我们想想,之前我们离线是枚举颜色,然后维护树上每个点询问的答案。如果是枚举树上的点,然后用个什么东西维护每种颜色的数量会不会好一些啊?

树上前缀和啊!

按照套路对树进行一遍 DFS,将询问标记在点上。我们维护一个 \(sum_x\) 表示根节点到当前节点的路径上颜色为 \(x\) 的节点的个数。在 DFS 的过程中实时维护 \(sum\) 数组,回溯的时候就减掉当前节点的贡献。

前缀和可以 \(\mathcal O(1)\) 的时间内维护,这样又少掉一个 \(\log\),可以通过此题。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
typedef pair<int,int> pii;
typedef long long ll;
const int MOD=1e9+7;
const int MAXN=1e5+5;
const int MAXV=1e7+5;
int n,q,qn,a[MAXN];
int hd[MAXN],nxt[MAXN<<1],to[MAXN<<1],ec=0;
void adde(int u,int v){
to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;
}
int fa[MAXN][22],dep[MAXN];
void dfs(int x,int f){
fa[x][0]=f;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dep[y]=dep[x]+1;dfs(y,x);
}
}
int getlca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;~i;i--) if(dep[x]-(1<<i)>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=20;~i;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
vector<pii> fac[MAXN];
struct Query{
int w,x,id,mul;
} qu[MAXN<<2];
int qpow(int x,int e){
int ret=1;
while(e){
if(e&1) ret=1ll*ret*x%MOD;
x=1ll*x*x%MOD;e>>=1;
} return ret;
}
int ans[MAXN];
int sum[MAXV/5][24];
vector<int> qv[MAXN];
int pid[MAXV],pr[MAXV/5],pcnt=0;
bitset<MAXV> vis;
void prework(){
for(int i=2;i<MAXV;i++){
if(!vis[i]){pr[++pcnt]=i;pid[i]=pcnt;}
for(int j=1;j<=pcnt&&pr[j]*i<MAXV;j++){
vis[pr[j]*i]=1;if(i%pr[j]==0) break;
}
}
}
void calc(int x){
ffe(it,fac[x]) sum[pid[it->fi]][it->se]++;
ffe(it,qv[x]){
int id=*it,tmp=qu[id].w;
vector<pii> ff;
for(int i=2;i*i<=tmp;i++){
if(tmp%i==0){
int cnt=0;
while(tmp%i==0) tmp/=i,cnt++;
ff.pb(mp(i,cnt));
}
}
if(tmp>1) ff.pb(mp(tmp,1));
int tot_mul=1;
for(int i=0;i<ff.size();i++){
int pw=1,val=ff[i].fi;
for(int j=1;j<=ff[i].se;j++) pw*=ff[i].fi;
for(int j=1;val<MAXV;j++,val*=ff[i].fi){
int cnt=sum[pid[ff[i].fi]][j];
int mul=qpow(min(val,pw),cnt);
tot_mul=1ll*tot_mul*mul%MOD;
}
}
if(qu[id].mul==1) ans[qu[id].id]=1ll*ans[qu[id].id]*tot_mul%MOD;
else ans[qu[id].id]=1ll*ans[qu[id].id]*qpow(tot_mul,MOD-2)%MOD;
}
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==fa[x][0]) continue;calc(y);
}
ffe(it,fac[x]) sum[pid[it->fi]][it->se]--;
}
int main(){
prework();
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);adde(u,v);adde(v,u);
}
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
int tmp=a[i];
for(int j=2;j*j<=tmp;j++){
if(tmp%j==0){
int cnt=0;
while(tmp%j==0) tmp/=j,cnt++;
fac[i].pb(mp(j,cnt));
}
}
if(tmp>1) fac[i].pb(mp(tmp,1));
}
dfs(1,0);
for(int i=1;i<=20;i++) for(int j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
scanf("%d",&q);
for(int i=1;i<=q;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);ans[i]=1;
int lca=getlca(u,v);
qu[++qn]={w,u,i,1};qu[++qn]={w,v,i,1};
qu[++qn]={w,lca,i,-1};if(lca!=1) qu[++qn]={w,fa[lca][0],i,-1};
}
for(int i=1;i<=qn;i++) qv[qu[i].x].pb(i);calc(1);
for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
return 0;
}

Codeforces 986E - Prince's Problem(树上前缀和)的更多相关文章

  1. [Codeforces 986E] Prince's Problem

    [题目链接] https://codeforces.com/contest/986/problem/E [算法] X到Y的路径积 , 可以转化为X到根的路径积乘Y到根的路径积 , 除以LCA到根的路径 ...

  2. 【题解】CF986E Prince's Problem(树上差分+数论性质)

    [题解]CF986E Prince's Problem(树上差分+数论性质) 题目大意: 给定你一棵树,有点权\(val_i\le 10^7\).现在有\(m\)组询问给定参数\(x,y,w\)问你对 ...

  3. Codeforces Round447 D树上前缀和

    已知完全二叉树和每条边的权值,q次询问,每次给出sta起点和H. w=(H-点到sta的权值),求w>0的所有w的加和. 这题用树上前缀和来写,e[i]记录子树上的点到点i的距离,sum[i][ ...

  4. Codeforces Gym 100637A A. Nano alarm-clocks 前缀和处理

    A. Nano alarm-clocks Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100637/p ...

  5. Educational Codeforces Round 11 C. Hard Process 前缀和+二分

    题目链接: http://codeforces.com/contest/660/problem/C 题意: 将最多k个0变成1,使得连续的1的个数最大 题解: 二分连续的1的个数x.用前缀和判断区间[ ...

  6. Codeforces Gym 100637A A. Nano alarm-clocks 前缀和

    A. Nano alarm-clocks Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100637/p ...

  7. codeforces 161D Distance in Tree 树上点分治

    链接:https://codeforces.com/contest/161/problem/D 题意:给一个树,求距离恰好为$k$的点对是多少 题解:对于一个树,距离为$k$的点对要么经过根节点,要么 ...

  8. Educational Codeforces Round 61 C 枚举 + 差分前缀和

    https://codeforces.com/contest/1132/problem/C 枚举 + 差分前缀和 题意 有一段[1,n]的线段,有q个区间,选择其中q-2个区间,使得覆盖线段上的点最多 ...

  9. Codeforces Global Round 2 D 差分 + 前缀和 + 二分

    https://codeforces.com/contest/1119/problem/D 题意 有n个数组,每个数组大小为\(10^{18}+1\)且为等差数列,给出n个数组的\(s[i]\),q次 ...

随机推荐

  1. python web1

    ***本篇中的测试均需要使用python3完成. 攻击以下面脚本运作的服务器. 针对脚本的代码逻辑,写出生成利用任意代码执行漏洞的恶意序列的脚本: 打开攻击机端口, 将生成的东西输入网页cookie: ...

  2. SLAM名词介绍

    gauge freedom:测量自由度 degrees-of-freedom(DoF) 自由度 wide-baseline matches:宽基线匹配 宽基线匹配:从描绘同一场景的两个或多个图像中建立 ...

  3. 如何使用远程工具连接Linux服务器

    大家好,今天我想和大家分享一下Linux如何连接远程控制工具我们都知道,Linux是著名的开源服务器操作系统,而在运维工程师的实际工作当中,我们不大可能时时刻刻都在服务器本地操作.因此这时,我们要用远 ...

  4. JVM:体系结构

    JVM:体系结构 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 概览 Java GC 主要回收的是 方法区 和 堆 中的内容 类加载器 类加载器是什么 双亲委派 ...

  5. MySQL:提高笔记-2

    MySQL:提高笔记-2 学完基础的语法后,进一步对 MySQL 进行学习,第一篇为:MySQL:提高笔记-1,这是第二篇内容 说明:这是根据 bilibili 上 黑马程序员 的课程 mysql入门 ...

  6. 6月2日 Scrum Meeting

    日期:2021年6月2日 会议主要内容概述: 取消账单类别自定义 图表属性分析取消函数输入 增加新的主题模板 一.进度情况 组员 负责 两日内已完成的工作 后两日计划完成的工作 工作中遇到的困难 徐宇 ...

  7. Linux入门需要搞清楚的思路问题

    很多同学接触linux不多,对linux平台的开发更是一无所知. 而现在的趋势越来越表明,作为一个优秀的软件开发人员,或计算机it行业从业人员,="" 掌握linux是一种很重要的 ...

  8. cf 12C Fruits(贪心【简单数学】)

    题意: m个水果,n个价格.每种水果只有一个价格. 问如果给每种水果分配价格,使得买的m个水果总价格最小.最大. 输出最小值和最大值. 思路: 贪心. 代码: bool cmp(int a,int b ...

  9. Serverless 工程实践|自建 Apache OpenWhisk 平台

    作者 | 刘宇(江昱) 前言:OpenWhisk 是一个开源.无服务器的云平台,可以在运行时容器中通过执行扩展的代码响应各种事件,而无须用户关心相关的基础设施架构. OpenWhisk 简介 Open ...

  10. 使用.NET6打造动态API

    ApiLite是直接将Service层自动生成api路由,可以不用添加Controller,支持模块插件化,在项目开发中能够提高工作效率,降低代码量. 开发环境 .NET SDK 6.0.100-rc ...