我很喜欢这道题。

题目大意:

给出一棵带点权树。对每个询问$ u,v,x $,求$\prod_{i \in P(u,v)}gcd(ai,x)$。其中$ P(u,v) $表示$ u $到$ v $的路径。

题目分析:

注意到权值大小不会超过$ 10^7 $,这似乎是在提示我们进行线性筛和质因数分解。我们就按照这个想法做。

其实我们分解完了之后我们会发现每个质数对于答案的影响是独立的,所以我们可以建素数个数个虚树。点数是多少呢?

对于每个点,它的点权$ai$只会分解出$O(log{ai})$种不同的素数。所以总共会分解出$O(nlog{a})$个点,这个上界是松的。

观察目标式,它是有这样的关系的:

$\prod_{i \in P(u,v)}gcd(a_i,x) = \prod_{i \in P(1,u)}gcd(a_i,x)*\prod_{i \in P(1,v)}gcd(a_i,x)*(\prod_{i \in P(1,lca(u,v))}gcd(a_i,x))^{-1}*(\prod_{i \in P(1,lca(u,v))}gcd(a_i,x))^{-1}*gcd(a_{lca},x) $

所以我们要做的仅仅是维护一个根节点到某个点$u$的答案。

考虑到我们上面已经分析出来每个素数是独立的,所以对于一个$x$,我们分别考虑它的每一个素因子$p$。一个质因子$p$和另一个质因子$q$的最大公约数一定是$1$,所以我们只考虑$x$的$p$和$a$的$p$的关系。

将$p$放进它对应的虚树中,我们查询的其实是${p^{\sum_{i \in P(1,h)}min(p_{ai},p_x)}}$(这一步变换可能太急了,这里用了一个初中知识点),其中$h$是查询点,$p_{ai}$是$ai$的$p$因子个数,$p_x同理$。没有在这个虚树中出现的路径上的点一定没有$p$这个因子,所以不用考虑。

对于上面那个式子,如果$p_{ai} \leq p_x$,起作用的就是$p_{ai}$,否则是$p_x$。这意味着我们需要在一个很快的时间内求出从$h$到根的虚树路径中有多少点的点权小于$p_x$,同时维护它们的和。这是一个简单的事情,大家都知道主席树可以做这个做到很快。但主席树必要吗?

从空间渐进意义上来讲,主席树是必要的,因为如果直接开空间会爆,但是如果空间开到了一个比较大的值或者我的上界分析得太松了,那么主席树是不必要的。原因在于对于每个素数$p$对应着一个原始的数据$ai$,使得$p^{p_{ai}} \leq ai$。所以$p_{ai}$是$O(loga)$级别的。所以虚树上的一次询问可以在$O(loga)$得到答复,虚数上每个点也只用开一个$ O(loga) $级别的桶存储。这样来说空间复杂度和时间复杂度是$ O(nlogxloga) $的。这里我们认为$O(n)=O(query)$。但是出于保险起见(换句话说,我无法证明这个上界有那么松),我们仍然采用主席树。

如果采用主席树,那么大家都晓得是从父亲节点那里作为历史状态。上面的分析告诉我们主席树只用开$O(loga)$作为长度。所以对于每个点,我们只会新增$ O(logloga) $个主席树的点。一次查询时间复杂度与空间相同,所以我们的空间复杂度是$ O(nlogxlogloga) $的。这里我们认为$O(n)=O(query)$。但是时间却不是和空间相同的,原因在于我们需要在每次结束后进行快速幂。我想了很久也没有想到如何克服快速幂的问题,所以时间与第一种做法相同,是$ O(qlogxloga) $的,因此在空间充裕的时候建议使用第一种做法。

upd:我阅读了题解,发现题解提供了一种空间更小的做法。就是对于每个质数建虚树然后在dfs的时候用树状数组维护一下即可。

upd2:我研究了一下codeforces,我认为它是这样测程序的。 首先采用不加优化跑,若TLE,则返回TLE,否则开O2重跑,返回运行时间。

代码:

 #include<bits/stdc++.h>
using namespace std; #define RI register int const int maxm = ;
const int N = ;
const int maxn = +;
const int Primenum = ;
const int mod = ; int prime[Primenum],flag[maxm],minn[maxm],num; int n,q,a[maxn];
vector<int> g[maxn];
vector<pair<int,int> > Control[Primenum],Fin[Primenum]; int RMQ[maxn<<][],euler[maxn<<],nE,app[maxn][]; int dep[maxn],fa[maxn],dfsin[maxn],dfsnum; vector <int> Vdfs[Primenum],Gotin[Primenum]; struct Ask{int u,v,x;}AQ[maxn];
struct node{int len,tot,ch[];}CMT[];
int numCMT; int Vfa[],Vfuck[],vnum;
vector <int> Vds[Primenum]; inline void fast_in(int &x){
x = ; char ch = getchar();
while(ch > '' || ch < '') ch = getchar();
while(ch <= '' && ch >= '') x = x*+ch-'',ch=getchar();
} inline int fast_pow(int now,int pw){
int ans = ,A = now,bit = ;
while(bit <= pw){
if(bit & pw) ans = (1ll*ans*A)%mod;
A = (1ll*A*A)%mod;
bit<<=;
}
return ans;
} inline void divide(int now){
int last = minn[a[now]],Numlast = ;
int p = a[now];
while(p != ){
if(minn[p] == last) Numlast++;
else{
Control[flag[last]].push_back(make_pair(now,Numlast));
last = minn[p];Numlast = ;
}
p /= minn[p];
}
Control[flag[last]].push_back(make_pair(now,Numlast));
} void dfs(int now,int f,int dp){
dep[now] = dp; fa[now] =f;dfsin[now] = ++num;
euler[++nE] = now;app[now][] = app[now][] = nE;
divide(now);
for(RI i=;i<g[now].size();++i){
if(g[now][i] == f) continue;
dfs(g[now][i],now,dp+);
euler[++nE] = now; app[now][] = nE;
}
} inline int QueryLCA(int u,int v){
int minn = min(app[u][],app[v][]),maxx = max(app[u][],app[v][]);
int k = ; while((<<k+) <= (maxx-minn+)) k++;
if(dep[RMQ[minn][k]] < dep[RMQ[maxx-(<<k)+][k]]){
return RMQ[minn][k];
}else return RMQ[maxx-(<<k)+][k];
} inline void BuildRMQ(){
for(RI i=;i<=nE;++i) RMQ[i][] = euler[i];
for(RI k=;(<<k)<=nE;k++)
for(RI i=;i<=nE;++i){
if(i+(<<k) > nE) RMQ[i][k] = RMQ[i][k-];
else{
if(dep[RMQ[i][k-]] < dep[RMQ[i+(<<k-)][k-]])
RMQ[i][k] = RMQ[i][k-];
else RMQ[i][k] = RMQ[i+(<<k-)][k-];
}
}
} inline void init(){
flag[] = ; minn[] = ;
for(RI i=;i<=N;++i){
if(!flag[i]){prime[++num]=i;flag[i]=num;minn[i]=i;}
for(RI j=;j<=num&&i*prime[j]<=N;++j){
flag[i*prime[j]] = ;
minn[i*prime[j]] = prime[j];
if(i%prime[j] == ) break;
}
}
dfs(,,);
BuildRMQ();
} inline void read(){
fast_in(n);
for(RI i=;i<n;++i){
int u,v;fast_in(u),fast_in(v);
g[u].push_back(v); g[v].push_back(u);
}
for(RI i=;i<=n;++i) fast_in(a[i]);
fast_in(q);
} int cmp(pair<int,int> alpha,pair<int,int> beta){
if(dfsin[alpha.first] < dfsin[beta.first]) return ;
else return ;
} inline int divisor(int now,int prm){
int len = ;
while(now%prm==)len++,now/=prm;
return len;
} inline void AddPoint(int last,int now,int tl,int tr,int data){
if(tl == tr){
if(data) CMT[now].len = CMT[last].len+;
else CMT[now].len = CMT[last].len;
CMT[now].tot = CMT[last].tot+data;
return;
}
int mid = (tl+tr)/;
if(data <= mid){
CMT[now].ch[] = CMT[last].ch[]; CMT[now].ch[] = ++numCMT;
AddPoint(CMT[last].ch[],CMT[now].ch[],tl,mid,data);
if(data) CMT[now].len = CMT[last].len+;
else CMT[now].len = CMT[last].len;
CMT[now].tot = CMT[last].tot+data;
}else{
CMT[now].ch[] = CMT[last].ch[]; CMT[now].ch[] = ++numCMT;
AddPoint(CMT[last].ch[],CMT[now].ch[],mid+,tr,data);
if(data) CMT[now].len = CMT[last].len+;
else CMT[now].len = CMT[last].len;
CMT[now].tot = CMT[last].tot+data;
}
} int sta[maxn],tp=; inline void BuildVirtualTree(){
for(RI i=;i<=num;++i){
sort(Control[i].begin(),Control[i].end(),cmp);
int SIZE = Control[i].size();
if(SIZE) Fin[i].push_back(Control[i][]);
for(RI j=;j<SIZE;++j){
Fin[i].push_back(Control[i][j]);
int ff = QueryLCA(Control[i][j].first,Control[i][j-].first);
Fin[i].push_back(make_pair(ff,divisor(a[ff],prime[i])));
}
sort(Fin[i].begin(),Fin[i].end(),cmp);
SIZE = unique(Fin[i].begin(),Fin[i].end())-Fin[i].begin();
while(Fin[i].size() != SIZE) Fin[i].pop_back();
for(RI j=;j<SIZE;++j) Vds[i].push_back(++vnum),Vdfs[i].push_back(dfsin[Fin[i][j].first]);
for(RI j=;j<SIZE;++j) Gotin[i].push_back();
for(RI j=;j<SIZE;++j){
int jpts = Fin[i][j].first;
while(tp&&QueryLCA(Fin[i][sta[tp]].first,jpts)!=Fin[i][sta[tp]].first)tp--;
if(tp) Vfuck[Vds[i][j]] = sta[tp],Vfa[Vds[i][j]] = Vds[i][sta[tp]];
sta[++tp]=j;
}
tp = ;
for(RI j=;j<SIZE;++j) {
if(Gotin[i][j]) continue;
int now = Vds[i][j];
if(Fin[i][j].second == ) Gotin[i][j] = Gotin[i][Vfuck[now]];
else{
Gotin[i][j] = ++numCMT;
AddPoint(Gotin[i][Vfuck[now]],Gotin[i][j],,,Fin[i][j].second);
}
}
}
} int QueryonTree(int now,int tl,int tr,int tag){
if(tl >= tag) return CMT[now].len*tag;
if(tr <= tag) return CMT[now].tot;
int mid = (tl+tr)/;
return QueryonTree(CMT[now].ch[],tl,mid,tag)+QueryonTree(CMT[now].ch[],mid+,tr,tag);
} inline int Query(int now,int im,int pts){
int TreeNum = flag[now];
int p=lower_bound(Vdfs[TreeNum].begin(),Vdfs[TreeNum].end(),dfsin[pts])-Vdfs[TreeNum].begin();
return fast_pow(now,QueryonTree(Gotin[TreeNum][p],,,im)); // 23 is const num 5E6 < 2^23 < 1E7
} inline void work(){
//puts("b");
for(RI i=;i<=q;++i){
fast_in(AQ[i].u);fast_in(AQ[i].v);fast_in(AQ[i].x);
int u = AQ[i].u,v=AQ[i].v,x=AQ[i].x;
int lca = QueryLCA(u,v),last = minn[x],lastNum = ,ans = ;
while(x!=){
if(last == minn[x]) lastNum++;
else{
if(a[u]%last!=)Control[flag[last]].push_back(make_pair(u,));
if(a[v]%last!=)Control[flag[last]].push_back(make_pair(v,));
last = minn[x],lastNum = ;
}
x/=minn[x];
}
if(a[u]%last!=)Control[flag[last]].push_back(make_pair(u,));
if(a[v]%last!=)Control[flag[last]].push_back(make_pair(v,));
}
//puts("h");
BuildVirtualTree();
for(RI i=;i<=q;++i){
int u = AQ[i].u,v=AQ[i].v,x=AQ[i].x;
int lca = QueryLCA(u,v),last = minn[x],lastNum = ,ans = ;
while(x!=){
if(last == minn[x]) lastNum++;
else{
ans = (1ll*ans*Query(last,lastNum,u)) % mod;
ans = (1ll*ans*Query(last,lastNum,v)) % mod;
int zz = fast_pow(Query(last,lastNum,lca),mod-);zz = (1ll*zz*zz)%mod;
ans = (1ll*ans*zz)%mod;
last = minn[x],lastNum = ;
}
x/=minn[x];
}
ans = (1ll*ans*Query(last,lastNum,u)) % mod;
ans = (1ll*ans*Query(last,lastNum,v)) % mod;
int zz = fast_pow(Query(last,lastNum,lca),mod-);zz = (1ll*zz*zz)%mod;
ans = (1ll*ans*zz)%mod;
ans = (1ll*ans*__gcd(a[lca],AQ[i].x))%mod;
printf("%d\n",ans);
}
} int main(){
read();
init();
work();
return ;
}

Codeforces986E Prince's Problem 【虚树】【可持久化线段树】【树状数组】的更多相关文章

  1. 主席树||可持久化线段树+离散化 || 莫队+分块 ||BZOJ 3585: mex || Luogu P4137 Rmq Problem / mex

    题面:Rmq Problem / mex 题解: 先离散化,然后插一堆空白,大体就是如果(对于以a.data<b.data排序后的A)A[i-1].data+1!=A[i].data,则插一个空 ...

  2. [BZOJ 4771]七彩树(可持久化线段树+树上差分)

    [BZOJ 4771]七彩树(可持久化线段树+树上差分) 题面 给定一棵n个点的有根树,编号依次为1到n,其中1号点是根节点.每个节点都被染上了某一种颜色,其中第i个节点的颜色为c[i].如果c[i] ...

  3. 【BZOJ-3218】a+b Problem 最小割 + 可持久化线段树

    3218: a + b Problem Time Limit: 20 Sec  Memory Limit: 40 MBSubmit: 1320  Solved: 498[Submit][Status] ...

  4. [POJ2104/HDU2665]Kth Number-主席树-可持久化线段树

    Problem Kth Number Solution 裸的主席树,模板题.但是求k大的时候需要非常注意,很多容易写错的地方.卡了好久.写到最后还给我来个卡空间. 具体做法参见主席树论文<可持久 ...

  5. BZOJ 3483 SGU505 Prefixes and suffixes(字典树+可持久化线段树)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3483 [题目大意] 给出一些串,同时给出m对前缀后缀,询问有多少串满足给出的前缀后缀模 ...

  6. 归并树 划分树 可持久化线段树(主席树) 入门题 hdu 2665

    如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想 今天学了一下两三种n*n*log(n)的数据结构 他们就是大名鼎鼎的 归并树 划分树 主席树,,,, 首先来说两个问题,,区间第k ...

  7. 主席树[可持久化线段树](hdu 2665 Kth number、SP 10628 Count on a tree、ZOJ 2112 Dynamic Rankings、codeforces 813E Army Creation、codeforces960F:Pathwalks )

    在今天三黑(恶意评分刷上去的那种)两紫的智推中,突然出现了P3834 [模板]可持久化线段树 1(主席树)就突然有了不详的预感2333 果然...然后我gg了!被大佬虐了! hdu 2665 Kth ...

  8. BZOJ.4771.七彩树(可持久化线段树)

    BZOJ 考虑没有深度限制,对整棵子树询问怎么做. 对于同种颜色中DFS序相邻的两个点\(u,v\),在\(dfn[u],dfn[v]\)处分别\(+1\),\(dfn[LCA(u,v)]\)处\(- ...

  9. BZOJ4771七彩树——可持久化线段树+set+树链的并+LCA

    给定一棵n个点的有根树,编号依次为1到n,其中1号点是根节点.每个节点都被染上了某一种颜色,其中第i个节 点的颜色为c[i].如果c[i]=c[j],那么我们认为点i和点j拥有相同的颜色.定义dept ...

随机推荐

  1. Python-可变参数和关键字参数(*args **kw)

    前言: Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数. 可变参数和关键字参数的语法: *args是可变参数,args接收的是一个tuple: **kw是关键 ...

  2. 实现多个标签页之间通信的几种方法(sharedworker)

      效果图.gif prologue 之前在网上看到一个面试题:如何实现浏览器中多个标签页之间的通信.我目前想到的方法有三种:使用websocket协议.通过localstorage.以及使用html ...

  3. 埋锅。。。BZOJ1004-置换群+burnside定理+

    看这道题时当时觉得懵逼...这玩意完全看不懂啊...什么burnside...难受... 于是去看了点视频和资料,大概懂了置换群和burnside定理,亦步亦趋的懂了别人的代码,然后慢慢的打了出来.. ...

  4. apply和call方法

    真伪数组转换 /* apply和call方法的作用: 专门用于修改方法内部的this 格式: call(对象, 参数1, 参数2, ...); apply(对象, [数组]); */ function ...

  5. XManager&XShell如何保存登录用户和登录密码

    Xshell配置ssh免密码登录 - qingfeng2556的博客 - CSDN博客https://blog.csdn.net/wuhenzhangxing/article/details/7948 ...

  6. Visual Studio 2010 Shortcuts

    Uploaded by ProNotion, updated on 11/28/2013 by jmb Platform: Windows/ English  PDF    Print   Hide ...

  7. 完美解决safari、微信浏览器下拉回弹效果

    CSS代码: .box{ overflow: auto; -webkit-overflow-scrolling: touch; } HTML代码: <body class="box&q ...

  8. JS刷新当前页面的几种方法总结

    reload 方法,该方法强迫浏览器刷新当前页面. 语法:location.reload([bForceGet]) 参数: bForceGet, 可选参数, 默认为 false,从客户端缓存里取当前页 ...

  9. Oracle 内存参数调优设置

    Oracle 数据库系统中起到调节作用的参数叫初始化参数,数据库管理员根据实际情况需要适当调整这些 初始化参数以优化Oracle系统. 1 主要系统参数调优介绍 2 系统内存参数的分配 2.1 Ora ...

  10. [转帖]wifi 4G 和 蓝牙的区别

    作者:沈万马链接:https://www.zhihu.com/question/64739486/answer/225227838来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...