Codeforces986E Prince's Problem 【虚树】【可持久化线段树】【树状数组】

我很喜欢这道题。

题目大意:

给出一棵带点权树。对每个询问$ 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重跑,返回运行时间。

代码:

 

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 
  4 #define RI register int
  5 
  6 const int maxm = 10000020;
  7 const int N = 10000000;
  8 const int maxn = 100000+5;
  9 const int Primenum = 780000;
 10 const int mod = 1000000007;
 11 
 12 int prime[Primenum],flag[maxm],minn[maxm],num;
 13 
 14 int n,q,a[maxn];
 15 vector<int> g[maxn];
 16 vector<pair<int,int> > Control[Primenum],Fin[Primenum];
 17 
 18 int RMQ[maxn<<1][19],euler[maxn<<1],nE,app[maxn][2];
 19 
 20 int dep[maxn],fa[maxn],dfsin[maxn],dfsnum;
 21 
 22 vector <int> Vdfs[Primenum],Gotin[Primenum];
 23 
 24 struct Ask{int u,v,x;}AQ[maxn];
 25 struct node{int len,tot,ch[2];}CMT[4000000];
 26 int numCMT;
 27 
 28 int Vfa[1500000],Vfuck[1500000],vnum;
 29 vector <int> Vds[Primenum];
 30 
 31 inline void fast_in(int &x){
 32     x = 0; char ch = getchar();
 33     while(ch > '9' || ch < '0') ch = getchar();
 34     while(ch <= '9' && ch >= '0') x = x*10+ch-'0',ch=getchar();
 35 }
 36 
 37 inline int fast_pow(int now,int pw){
 38     int ans = 1,A = now,bit = 1;
 39     while(bit <= pw){
 40     if(bit & pw) ans = (1ll*ans*A)%mod;
 41     A = (1ll*A*A)%mod;
 42     bit<<=1;
 43     }
 44     return ans;
 45 }
 46 
 47 inline void divide(int now){
 48     int last = minn[a[now]],Numlast = 0;
 49     int p = a[now];
 50     while(p != 1){
 51     if(minn[p] == last) Numlast++;
 52     else{
 53         Control[flag[last]].push_back(make_pair(now,Numlast));
 54         last = minn[p];Numlast = 1;
 55     }
 56     p /= minn[p];
 57     }
 58     Control[flag[last]].push_back(make_pair(now,Numlast));
 59 }
 60 
 61 void dfs(int now,int f,int dp){
 62     dep[now] = dp; fa[now] =f;dfsin[now] = ++num;
 63     euler[++nE] = now;app[now][0] = app[now][1] = nE;
 64     divide(now);
 65     for(RI i=0;i<g[now].size();++i){
 66     if(g[now][i] == f) continue;
 67     dfs(g[now][i],now,dp+1);
 68     euler[++nE] = now; app[now][1] = nE;
 69     }
 70 }
 71 
 72 inline int QueryLCA(int u,int v){
 73     int minn = min(app[u][0],app[v][0]),maxx = max(app[u][1],app[v][1]);
 74     int k = 0; while((1<<k+1) <= (maxx-minn+1)) k++;
 75     if(dep[RMQ[minn][k]] < dep[RMQ[maxx-(1<<k)+1][k]]){
 76     return RMQ[minn][k];
 77     }else return RMQ[maxx-(1<<k)+1][k];
 78 }
 79 
 80 inline void BuildRMQ(){
 81     for(RI i=1;i<=nE;++i) RMQ[i][0] = euler[i];
 82     for(RI k=1;(1<<k)<=nE;k++)
 83     for(RI i=1;i<=nE;++i){
 84         if(i+(1<<k) > nE) RMQ[i][k] = RMQ[i][k-1];
 85         else{
 86         if(dep[RMQ[i][k-1]] < dep[RMQ[i+(1<<k-1)][k-1]])
 87             RMQ[i][k] = RMQ[i][k-1];
 88         else RMQ[i][k] = RMQ[i+(1<<k-1)][k-1];
 89         }
 90     }
 91 }
 92 
 93 inline void init(){
 94     flag[1] = 1; minn[1] = 1;
 95     for(RI i=1;i<=N;++i){
 96     if(!flag[i]){prime[++num]=i;flag[i]=num;minn[i]=i;}
 97     for(RI j=1;j<=num&&i*prime[j]<=N;++j){
 98         flag[i*prime[j]] = 1;
 99         minn[i*prime[j]] = prime[j];
100         if(i%prime[j] == 0) break;
101     }
102     }
103     dfs(1,0,1);
104     BuildRMQ();
105 }
106 
107 inline void read(){
108     fast_in(n);
109     for(RI i=1;i<n;++i){
110     int u,v;fast_in(u),fast_in(v);
111     g[u].push_back(v); g[v].push_back(u);
112     }
113     for(RI i=1;i<=n;++i) fast_in(a[i]);
114     fast_in(q);
115 }
116 
117 int cmp(pair<int,int> alpha,pair<int,int> beta){
118     if(dfsin[alpha.first] < dfsin[beta.first]) return 1;
119     else return 0;
120 }
121 
122 inline int divisor(int now,int prm){
123     int len = 0;
124     while(now%prm==0)len++,now/=prm;
125     return len;
126 }
127 
128 inline void AddPoint(int last,int now,int tl,int tr,int data){
129     if(tl == tr){
130     if(data) CMT[now].len = CMT[last].len+1;
131     else CMT[now].len = CMT[last].len;
132     CMT[now].tot = CMT[last].tot+data;
133     return;
134     }
135     int mid = (tl+tr)/2;
136     if(data <= mid){
137     CMT[now].ch[1] = CMT[last].ch[1]; CMT[now].ch[0] = ++numCMT;
138     AddPoint(CMT[last].ch[0],CMT[now].ch[0],tl,mid,data);
139     if(data) CMT[now].len = CMT[last].len+1;
140     else CMT[now].len = CMT[last].len;
141     CMT[now].tot = CMT[last].tot+data;
142     }else{
143     CMT[now].ch[0] = CMT[last].ch[0]; CMT[now].ch[1] = ++numCMT;
144     AddPoint(CMT[last].ch[1],CMT[now].ch[1],mid+1,tr,data);
145           if(data) CMT[now].len = CMT[last].len+1;
146     else CMT[now].len = CMT[last].len;
147     CMT[now].tot = CMT[last].tot+data;
148     }
149 }
150 
151 int sta[maxn],tp=0;
152 
153 inline void BuildVirtualTree(){
154     for(RI i=1;i<=num;++i){
155     sort(Control[i].begin(),Control[i].end(),cmp);
156     int SIZE = Control[i].size();
157     if(SIZE) Fin[i].push_back(Control[i][0]);
158     for(RI j=1;j<SIZE;++j){
159         Fin[i].push_back(Control[i][j]);
160         int ff = QueryLCA(Control[i][j].first,Control[i][j-1].first);
161         Fin[i].push_back(make_pair(ff,divisor(a[ff],prime[i])));
162     }
163     sort(Fin[i].begin(),Fin[i].end(),cmp);
164     SIZE = unique(Fin[i].begin(),Fin[i].end())-Fin[i].begin();
165     while(Fin[i].size() != SIZE) Fin[i].pop_back();
166     for(RI j=0;j<SIZE;++j) Vds[i].push_back(++vnum),Vdfs[i].push_back(dfsin[Fin[i][j].first]);
167     for(RI j=0;j<SIZE;++j) Gotin[i].push_back(0);
168     for(RI j=0;j<SIZE;++j){
169         int jpts = Fin[i][j].first;
170       while(tp&&QueryLCA(Fin[i][sta[tp]].first,jpts)!=Fin[i][sta[tp]].first)tp--;
171       if(tp) Vfuck[Vds[i][j]] = sta[tp],Vfa[Vds[i][j]] = Vds[i][sta[tp]];
172         sta[++tp]=j;
173     }
174     tp = 0;
175     for(RI j=0;j<SIZE;++j) {
176         if(Gotin[i][j]) continue;
177         int now = Vds[i][j];
178         if(Fin[i][j].second == 0) Gotin[i][j] = Gotin[i][Vfuck[now]];
179         else{
180         Gotin[i][j] = ++numCMT;
181         AddPoint(Gotin[i][Vfuck[now]],Gotin[i][j],1,23,Fin[i][j].second);
182         }
183     }
184     }
185 }
186 
187 int QueryonTree(int now,int tl,int tr,int tag){
188     if(tl >= tag) return CMT[now].len*tag;
189     if(tr <= tag) return CMT[now].tot;
190     int mid = (tl+tr)/2;
191     return QueryonTree(CMT[now].ch[0],tl,mid,tag)+QueryonTree(CMT[now].ch[1],mid+1,tr,tag);
192 }
193 
194 inline int Query(int now,int im,int pts){
195     int TreeNum = flag[now];
196     int p=lower_bound(Vdfs[TreeNum].begin(),Vdfs[TreeNum].end(),dfsin[pts])-Vdfs[TreeNum].begin();
197     return fast_pow(now,QueryonTree(Gotin[TreeNum][p],1,23,im)); // 23 is const num 5E6 < 2^23 < 1E7
198 }
199 
200 inline void work(){
201     //puts("b");
202     for(RI i=1;i<=q;++i){
203     fast_in(AQ[i].u);fast_in(AQ[i].v);fast_in(AQ[i].x);
204     int u = AQ[i].u,v=AQ[i].v,x=AQ[i].x;
205     int lca = QueryLCA(u,v),last = minn[x],lastNum = 0,ans = 1;
206     while(x!=1){
207         if(last == minn[x]) lastNum++;
208         else{
209         if(a[u]%last!=0)Control[flag[last]].push_back(make_pair(u,0));
210         if(a[v]%last!=0)Control[flag[last]].push_back(make_pair(v,0));
211         last = minn[x],lastNum = 1;
212         }
213         x/=minn[x];
214     }
215     if(a[u]%last!=0)Control[flag[last]].push_back(make_pair(u,0));
216     if(a[v]%last!=0)Control[flag[last]].push_back(make_pair(v,0));
217     }
218     //puts("h");
219     BuildVirtualTree();
220     for(RI i=1;i<=q;++i){
221     int u = AQ[i].u,v=AQ[i].v,x=AQ[i].x;
222     int lca = QueryLCA(u,v),last = minn[x],lastNum = 0,ans = 1;
223     while(x!=1){
224         if(last == minn[x]) lastNum++;
225         else{
226         ans = (1ll*ans*Query(last,lastNum,u)) % mod;
227         ans = (1ll*ans*Query(last,lastNum,v)) % mod;
228         int zz = fast_pow(Query(last,lastNum,lca),mod-2);zz = (1ll*zz*zz)%mod;
229         ans = (1ll*ans*zz)%mod;
230         last = minn[x],lastNum = 1;
231         }
232         x/=minn[x];
233     }
234     ans = (1ll*ans*Query(last,lastNum,u)) % mod;
235     ans = (1ll*ans*Query(last,lastNum,v)) % mod;
236     int zz = fast_pow(Query(last,lastNum,lca),mod-2);zz = (1ll*zz*zz)%mod;
237     ans = (1ll*ans*zz)%mod;
238     ans = (1ll*ans*__gcd(a[lca],AQ[i].x))%mod;
239     printf("%d\n",ans);
240     }
241 }
242 
243 int main(){
244     read();
245     init();
246     work();
247     return 0;
248 }

 

转载于:https://www.cnblogs.com/Menhera/p/9123433.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值