2021.02.28 一些好题的记录(二)

4 篇文章 0 订阅
3 篇文章 0 订阅

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 tk,tk(mod2)

答案就是 Yes(因为你可以在任何一条边上走来走去,每次走将会多出 2 2 2 的长度,毕竟是一来一回)。

于是我们只需要求出从 a a a b b b 的路径长度即可。

路径分成 3 3 3 种:

  1. 直接从 a a a b b b(即原树上的边)
  2. a a a x x x 再到 y y y 再到 b b b
  3. 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+11),即 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+11],那么 ( i , j ) (i,j) (i,j) 是满足条件的。

但是我们还要考虑这样的情况:第 k k k 位进位了,然后第 k − 1 k-1 k1 位进位了,最后第 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+22]

现在的问题是,如何快速的求出有多少对 ( 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 1 处,注意 i i i 应该是倒序枚举,否则 Two-pointers 是错的。
  2. 代码 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 3 处:注意上面我们把每个 ( i , j ) (i,j) (i,j) 算了两次( i i i 一次, j j j 一次),所以注意先除以二。
  4. 代码涉及位运算处:注意位运算的优先级低于加减,所以勤加括号。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值