0%

题目大意

给定一棵有根多叉树,包含 NN 个节点,树根为节点 SS。有 MM 次询问,每次询问给定两个节点 aabb,求它们的最近公共祖先(LCA,Lowest Common Ancestor)。

输入格式:

  • 第一行:NN MM SS(节点数、询问数、根节点)

  • 接下来 N1N-1 行:每行两个整数 xx yy,表示 xxyy 之间有一条边

  • 接下来 MM 行:每行两个整数 aa bb,表示询问 aabb 的最近公共祖先

输出格式:

  • MM 行,每行一个整数,表示对应询问的答案

思路讲解

重点在于这两个dfs,其实还挺简单的。dfs负责找出重儿子,dfss负责重链剖分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void dfs(int u){	// 找出一个节点的重儿子
siz[u]=1;
dep[u]=dep[fa[u]]+1;
for(auto &v:g[u]){
if(v==fa[u]) continue;
fa[v]=u;
dfs(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]){
son[u]=v;
}
}
}
void dfss(int u,int up){ // 维护top节点
top[u]=up;
if(son[u]){
dfss(son[u],up);
}
for(auto &v:g[u]){
if(v==son[u]) continue;
if(v==fa[u]) continue;
// 是一条新的重链
dfss(v,v);
}
}

lca的求法。

1
2
3
4
5
6
7
8
9
10
11
int lca(int u,int v){
while(top[u]!=top[v]){ // 判断两点在不在一条链上面
if(dep[top[u]]>dep[top[v]]){ // 要跳到更深的链上面
// 必须要加一个fa,否则就走不出这条链了,死循环了
u=fa[top[u]];
}else{
v=fa[top[v]];
}
}
return dep[u]<dep[v]?u:v;
}

AC代码

心路历程(WA,TLE,MLE……)

思路讲解

就是暴力枚举一下就行了。当然要预处理一下。

AC代码

https://acm.hdu.edu.cn/contest/view-code?cid=1172&rid=25007

心路历程(WA,TLE,MLE……)

思路讲解

首先根据容斥原理,并集=全集-交集。

1
2
3
4
FOR(i,1,N){
ll ans=rka[i]+rkb[i]-2-jiao[i];
cout<<ans<<" ";
}

那么问题就来到了如何求这个交集。那么我们排列(首先根据rka)以后,只有前面的才有可能是在这个交集之中。

1
2
3
4
5
FOR(i,1,N){
ele.EB(rka[i],rkb[i],0);
ele.EB(rka[i],rkb[i],1);
}
sort(all(ele));

那么问题就来到了有几个jj,使得 rkbj<rkbirkb_j<rkb_i 了,这个可以用树状数组解决(类似于逆序对,我们只用考虑前面的比我们大的数字,这里我们也只用考虑前面的,因为后面 rkarka 不满足)。

1
2
3
4
5
6
7
8
for(auto &[x,y,isq]:ele){
ll a=A[x];
if(isq){
jiao[a]=tr.query(1,y-1);
}else{
tr.add(y,1);
}
}

AC代码

https://acm.hdu.edu.cn/contest/view-code?cid=1173&rid=10158

心路历程(WA,TLE,MLE……)

思路讲解

不难发现,这个x坐标完全取决于奇数操作,y坐标完全取决于偶数操作。

在y有y个操作下,其可以产生 [y+1,2y][y+1,2^{y}] 种子集和(就是这个范围内的都能生成)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FOR(i,1,N){
if(i*i>N) break;
if(N%i==0){
ll di=N/i;
FOR(j,1,N){
ll y=j/2,x=(j+1)/2;
if(binpow(2,y)>N) break;
if(i>=x && i<=binpow(2,x-1) && di<=binpow(2,y) && di>=y+1){
ans=min(ans,j);
}
if(di>=x && di<=binpow(2,x-1) && i<=binpow(2,y) && i>=y+1){
ans=min(ans,j);
}
}
}
}

AC代码

心路历程(WA,TLE,MLE……)

思路讲解

三个集合的容斥原理

$\left | A \cup B \cup C \right | = \left | A \right | + \left | B \right | + \left | C \right | - \left | A \cap B \right | - \left | A \cap C \right | - \left | B \cap C \right | + \left | A \cap B \cap C \right | $

image

那么不难发现规律,就是奇加偶减。这就是这段代码的由来。

1
2
3
ll cn=bi.count();
if(cn%2==1) del+=(sz+1)*sz/2;
else del-=(sz+1)*(sz)/2;

那么直接计算合法路径数量比较难,但是我们可以枚举不合法子集。如果s是101(二进制),那么就是第一个素因子要不符合要求(LCM只关注这些数当中的最大的,你可以理解为对素因子取了一个并集,那么gcd就是取了一个交集)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// s如果这一位是1,就代表这一位的质因数必须不满足要求
FOR(s,0,(1<<siz)-1){
vb vis(N+5,0);
bitset<11> bi(s);
// 检查该数字(该点)是否满足要求
auto check=[&](ll u)->bool{
FOR(i,0,siz-1){
if(!bi[i]) continue;
// 该数不满足要求,因为我们要求这一位质因数数量缺失,或者太多
if(fach[u][i]==cnt[i]){
return false;
}
}
return true;
};
FOR(u,1,N){
if(vis[u]) continue;
if(!valid[u]) continue;
vis[u]=true;
if(!check(u)) continue;
queue<ll> q;q.push(u);
ll sz=0; // 维护联通块内的点的数量
while(!q.empty()){
ll u=q.front();q.pop();
vis[u]=true;
++sz;
for(auto v:g[u]){
if(vis[v]) continue;
if(!valid[v]) continue;
if(!check(v)) continue;
q.push(v);
}
}
// sz*(sz-1)/2+sz
ll cn=bi.count();
if(cn%2==1) del+=(sz+1)*sz/2;
else del-=(sz+1)*(sz)/2;
// ans+=(sz+1)*sz/2;
}
#ifdef LOCAL
debug(del);
#endif
}
ll ans=-del;
cout<<ans<<"\n";
}

AC代码

https://acm.hdu.edu.cn/contest/view-code?cid=1172&rid=22776

心路历程(WA,TLE,MLE……)