CF1304E 1-Trees and Queries \color{green}{\texttt{CF1304E 1-Trees and Queries}} CF1304E 1-Trees and Queries
[Problem] \color{blue}{\texttt{[Problem]}} [Problem]
[Solution] \color{blue}{\texttt{[Solution]}} [Solution]
借鉴 NOIp2019 PJ 的第 4 4 4 题的思路,我们可以发现,如果存在一条从 a a a 到 b b b 的长度为 t t t 的路,则只要同时满足:
t ≤ k , t ≡ k ( m o d 2 ) t \leq k,t \equiv k \pmod 2 t≤k,t≡k(mod2)
答案就是 Yes
(因为你可以在任何一条边上走来走去,每次走将会多出
2
2
2 的长度,毕竟是一来一回)。
于是我们只需要求出从 a a a 到 b b b 的路径长度即可。
路径分成 3 3 3 种:
- 直接从 a a a 到 b b b(即原树上的边)
- 从 a a a 到 x x x 再到 y y y 再到 b b b
- 从 a a a 到 y y y 再到 x x x 再到 b b b
用 LCA
算法求出树上两点间距离,直接判断即可。
[code] \color{blue}{\texttt{[code]}} [code]
const int N=1e5+100;
struct edge{//链式前向星
int next,to;//的模板
}e[N<<1];int h[N],te;
inline void add(int u,int v){
e[++te]=(edge){h[u],v};h[u]=te;
}
int dep[N],f[22][N],n,q;
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
for(int i=1;i<=20;i++)
f[i][u]=f[i-1][f[i-1][u]];
for(int i=h[u];i;i=e[i].next){
register int v=e[i].to;
if (v==fa) continue;//判重
f[0][v]=u;dfs(v,u);
}
}
inline int LCA(int u,int v){
if (dep[u]<dep[v]) swap(u,v);
for(int i=20;i>=0;i--)
if (dep[f[i][u]]>=dep[v])
u=f[i][u];
if (u==v) return u;
for(int i=20;i>=0;i--)
if (f[i][u]!=f[i][v])
u=f[i][u],v=f[i][v];
return f[0][u];
}
inline int dist(int u,int v){
return dep[u]+dep[v]-2*dep[LCA(u,v)];
}
inline bool check(int t,int k){
return t<=k&&(k-t)%2==0;
}
int main(){
n=read();//输入树的大小
for(int i=1,u,v;i<n;i++){
u=read();v=read();
add(u,v);add(v,u);
}
dfs(1,0);q=read();
for(int i=1;i<=q;i++){
int x=read(),y=read(),a=read(),b=read(),k=read();
int ab=dist(a,b),ax=dist(a,x),ay=dist(a,y),bx=dist(b,x),by=dist(b,y);
if (check(ab,k)||check(ax+by+1,k)||check(ay+bx+1,k)) printf("YES\n");
else printf("NO\n");
}
return 0;
}
CF1322B Present \color{green}{\texttt{CF1322B Present}} CF1322B Present
[Problem] \color{blue}{\texttt{[Problem]}} [Problem]
[Solution] \color{blue}{\texttt{[Solution]}} [Solution]
因为是二进制的操作,所以我们考虑二进制下的答案(下面的讨论是针对二进制的)。
显然每一位是互相独立的,所以我们只考虑第 k k k 位,最后在统计每一位的答案即为总答案。
记 b i = a i m o d ( 2 k + 1 − 1 ) b_{i}=a_{i} \mod \left ( 2^{k+1} -1 \right ) bi=aimod(2k+1−1),即 a i a_{i} ai 的末 k k k 位值。
如果第 k k k 位对答案有贡献,则代表有奇数个 b i + b j b_{i}+b_{j} bi+bj 的第 k k k 位为 1 1 1。
现在的问题是,如何求出有多少对 ( i , j ) (i,j) (i,j) 满足 b i + b j b_{i}+b_j bi+bj 的第 k k k 位为 1 1 1。
如果 b i + b j ∈ [ 2 k , 2 k + 1 − 1 ] b_{i}+b_{j} \in [2^k,2^{k+1}-1] bi+bj∈[2k,2k+1−1],那么 ( i , j ) (i,j) (i,j) 是满足条件的。
但是我们还要考虑这样的情况:第 k k k 位进位了,然后第 k − 1 k-1 k−1 位进位了,最后第 k k k 位为 1 1 1。这个情况有点复杂,但是我们可以证明,此时 b i + b j ∈ [ 2 k + 1 + 2 k , 2 k + 2 − 2 ] b_i+b_j \in [2^{k+1}+2^k,2^{k+2}-2] bi+bj∈[2k+1+2k,2k+2−2]。
现在的问题是,如何快速的求出有多少对 ( i , j ) (i,j) (i,j) 满足 b i + b j ∈ [ L , R ] b_{i}+b_{j} \in[L,R] bi+bj∈[L,R]。
显然,
b
b
b 数组内部的顺序对答案没有影响,所以我们将
b
b
b 数组排序。然后的事情就可以用 Two-pointers
解决了。
[code] \color{blue}{\texttt{[code]}} [code]
const int N=4e5+100;
int a[N],b[N],n,ans;
inline int count(int u,int v){
register int sum=0;//统计和
for(int i=n,l=1,r=1;i>=1;i--){// *1
while (l<=n&&b[i]+b[l]<u) l++;
while (r<=n&&b[i]+b[r]<=v) r++;
sum+=r-l-(l<=i&&i<r);//*2
}
return (sum>>1)&1;//*3
}
inline int calc(int k){
for(int i=1;i<=n;i++)
b[i]=a[i]&((1<<(k+1))-1);
sort(b+1,b+n+1);//记得排序
return count(1<<k,(1<<(k+1))-1)^count(3<<k,(1<<(k+2))-2);
}
inline void calc_answer(){
for(int i=0;i<27;i++)
ans+=calc(i)<<i;
}
int main(){
n=read();ans=0;
for(int i=1;i<=n;i++)
a[i]=read();
calc_answer();
printf("%d",ans);
return 0;
}
需要注意的代码细节:
- 代码
1
1
1 处,注意
i
i
i 应该是倒序枚举,否则
Two-pointers
是错的。 - 代码 2 2 2 处:注意当 i ∈ [ l , r ] i \in [l,r] i∈[l,r] 时, i i i 是不能算入 sum \texttt{sum} sum,因为任意 ( i , i ) (i,i) (i,i) 都是不合法的。
- 代码 3 3 3 处:注意上面我们把每个 ( i , j ) (i,j) (i,j) 算了两次( i i i 一次, j j j 一次),所以注意先除以二。
- 代码涉及位运算处:注意位运算的优先级低于加减,所以勤加括号。