我很喜欢这道题。

题目大意:

给出一棵带点权树。对每个询问$ 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. python2中reload(sys)后设置编码

    python在安装时,默认的编码是ascii,当程序中出现非ascii编码时,python的处理常常会报这样的错UnicodeDecodeError: 'ascii' codec can't deco ...

  2. 小P的字符串

    题目描述 小P最近在研究字符编码,给出一串由0.1组成的字符串,从中任意进行截取,如果截取的字符串对应一个英文字母的ASCII值,小P就把这个0.1串叫字母子串,问给定的字符串最多能截取出多少个字母子 ...

  3. struts2之配置文件struts.xml详解

    struts配置文件 struts.xml配置参数详解 struts.xml中很大一部分配置默认配置就好了 但是有些还是需要做了解  以便于理解 和修改 <?xml version=" ...

  4. UnderWater+SDN论文之二

    ---- Software-defined underwater acoustic networking platform and its applications source: Ad Hoc Ne ...

  5. R语言

    什么是R语言编程? R语言是一种用于统计分析和为此目的创建图形的编程语言.不是数据类型,它具有用于计算的数据对象.它用于数据挖掘,回归分析,概率估计等领域,使用其中可用的许多软件包. R语言中的不同数 ...

  6. anaconda 出现add 。。。进不去

    找到.condarc 文件  C:\Users\leiyi内 把里面内容替换为 channels: - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pk ...

  7. YCSB报": No such file or directory"异常

    异常信息如下: 文件路径.权限都没有问题. 上网遍寻无果,安装流程与官网一致,开始怀疑是环境问题,后来用别人能用的YCSB复制到本地,却能正常运行. 后来修改了ycsb文件,加了个空格,保存退出,再运 ...

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

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

  9. PHP之位运算符

    使用场景: 1) 判断奇数偶数 ; $i < ; $i++) { ){ echo $i.PHP_EOL; } } //输出所有奇数 2)快速修改状态 $status1 = ; $status2 ...

  10. PHP常见错误汇总

    日常开发和调试的时候,经常会遇到一些错误,光怪陆离的不知所以,所以,特此将错误汇总一下,借鉴!!! 1. 原因分析:  一般可能是该文件出现了问题,检查一下代码和格式,是否出现开始的地方出现了空格,或 ...