首页 > 其他分享 >LuoguP5354 [Ynoi2017]由乃的OJ题解

LuoguP5354 [Ynoi2017]由乃的OJ题解

时间:2023-02-20 13:22:30浏览次数:51  
标签:OJ int 题解 top ull LuoguP5354 lef1 op mathrm

P5354 [Ynoi2017]由乃的OJ

Preface

自己的由乃题一血,写篇题解纪念一下。

Description

给定一颗 \(n\) 个结点的树,每个结点都有一个权值 \(a_i\) 和一个位运算符 \(\mathrm{op}_i\)。

有 \(m\) 次操作,包含以下两种:

  • 给定 \(x\),修改 \(a_x\) 与 \(\mathrm{op}_x\);
  • 给定 \(x,y,z\),求 \(v\in [0..z]\) 使得 \(v\) 沿点 \(x\) 到点 \(y\) 的路径移动,移动到点 \(i\) 就会进行操作 \(v \ \mathrm{op}_i \ a_i \to v\),在完成所有操作之后 \(v\) 最大,求出这个最大值。

\(n,m \le 10^5,\mathrm{op} \in \{\mathrm{and,or,xor}\}\),任意时刻 \(a_i,z < 2^k\),\(k \le 64\)。

Sol

思路不是那么好想的树剖+线段树。

直接考虑用树剖转换为区间问题。

由于本题中的运算都是位运算,因此每一位是独立的,这一点就可以考虑使用线段树维护某个区间内每一位在 \(v\) 的对应位取 \(0/1\) 时这一位的最终运算结果。

举个例子:假设区间为 \((1,\mathrm{and}),(3,\mathrm {xor}),(2,\mathrm{or})\),数值转写为二进制后为 \(01,11,10\),那么 \(v\) 从左往右低到高第 \(i\) 位的取值为 \(j\) 时的运算结果如下:

\(j=0\) \(j=1\)
\(i=1\) \(1\) \(0\)
\(i=2\) \(1\) \(1\)

这个信息是可以 \(O(k)\) 合并的,只要把把左边的值代入到右边进行求值即可。

由于从左往右的答案和从右往左的答案不一定一样,并且树剖中线段树维护的区间是在链上从上到下编号的,故在链合并的时候需要两个方向的信息(\(u\) 到 \(\mathrm{LCA}(u,v)\) 为向上,\(\mathrm{LCA(u,v)}\) 到 \(v\) 为向下),因此两个方向的信息都要维护。

我们回到原问题考虑如何计算答案。

我们采用贪心策略,从 \(z\) 范围内的最高位开始贪。考虑到 \(2^x > \sum_{i=1}^{x-1}2^i\),因此如果在第 \(x\) 位上存在一种取值使得在进行了操作之后结果的这一位上能取到 \(1\) 并且符合值域要求,那么我们就一定能要这一位贪下来,在答案上加上 \(2^x\)。

到此问题就解决了。时间复杂度 \(O(mk\log^2 n)\)。

然后您看到这道题250ms的时限和 \(k=64\) 陷入了沉思。

这复杂度显然跑不过好吧。想办法优化。

方法也很简单:我们充分利用位运算性质,把上述的信息压进 unsigned long long,就把 \(k\) 成功优化掉了。

最终时间复杂度 \(O(m \log^2 n)\)。

细节详见代码。

Tips

  • 链合并的细节非常多,详见代码。

Code

#include<cstdio>
#include<algorithm>
const int M=1e5+5;
typedef unsigned long long ull;
inline int read(){int x(0),op(0);char ch=getchar();while(ch<'0'||ch>'9')op|=(ch==45),ch=getchar();while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();return op?-x:x;}
inline int ssread(){int x;scanf("%d",&x);return x;}
inline ull uread(){ull x;scanf("%llu",&x);return x;}
#ifndef ONLINE_JUDGE
#define read ssread
#endif
int n,m,K;
struct edge{
	int v,nxt;
}e[M<<1];
int etot,h[M];
int fa[M],dep[M],top[M],dfn[M],siz[M],son[M],rev[M],DFN;
ull a[M];
int OP[M];
void dfs1(int u,int f){//树剖第一次dfs
	fa[u]=f;dep[u]=dep[f]+1;siz[u]=1;
	for(int i=h[u],v;v=e[i].v,i;i=e[i].nxt)if(v!=f){
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}
void dfs2(int u,int tp){//树剖第二次dfs
	dfn[u]=++DFN;rev[DFN]=u;top[u]=tp;
	if(!son[u])return;
	dfs2(son[u],tp);
	for(int i=h[u],v;v=e[i].v,i;i=e[i].nxt)if(v!=son[u]&&v!=fa[u])dfs2(v,v);
}
struct node{//用于记录区间信息的结构体
	ull lef0,lef1,rig0,rig1;
	/*
	lef0:从左开始走,取0得到的值
	lef1:从左开始走,取1得到的值
	rig0:从右开始走,取0得到的值
	rig1:从右开始走,取1得到的值
	*/
	node(ull x=0,ull y=-1,ull z=0,ull w=-1){lef0=x,lef1=y,rig0=z,rig1=w;}
};
node merge(node x,node y){
	return (node){
		//这里的逻辑是:枚举靠前区间的值,以此计算靠后区间的值
		(~x.lef0&y.lef0)|(x.lef0&y.lef1),
		(x.lef1&y.lef1)|(~x.lef1&y.lef0),
		(~y.rig0&x.rig0)|(y.rig0&x.rig1),
		(y.rig1&x.rig1)|(~y.rig1&x.rig0)
	};
}
namespace segtree{//线段树
	#define ls (p<<1)
	#define rs ((p<<1)|1)
	#define mid ((s+t)>>1)
	node T[M<<2];
	void pushup(int p){
		T[p]=merge(T[ls],T[rs]);
	}
	void setp(int p,ull x,int op){//设置某个点的状态
		if(op==1)T[p].lef0=T[p].rig0=0,T[p].lef1=T[p].rig1=x;
		else if(op==2)T[p].lef0=T[p].rig0=x,T[p].lef1=T[p].rig1=-1;
		else T[p].lef0=T[p].rig0=x,T[p].lef1=T[p].rig1=~x;
	}
	void build(int p=1,int s=1,int t=n){
		if(s==t){
			setp(p,a[rev[s]],OP[rev[s]]);
			return;
		}
		build(ls,s,mid);build(rs,mid+1,t);
		pushup(p);
	}
	void modify(int pos,ull x,int op,int p=1,int s=1,int t=n){
		if(s==t){
			setp(p,x,op);
			return;
		}
		if(pos<=mid)modify(pos,x,op,ls,s,mid);
		else modify(pos,x,op,rs,mid+1,t);
		pushup(p);
	}
	node query(int l,int r,int p=1,int s=1,int t=n){
		if(l<=s&&t<=r)return T[p];
		if(l<=mid){
			if(r>mid)return merge(query(l,r,ls,s,mid),query(l,r,rs,mid+1,t));
			else return query(l,r,ls,s,mid);
		}
		else return query(l,r,rs,mid+1,t);
	//一定要注意区间的合并问题!
	}
}
void mdf(int pos,ull x,int op){
	segtree::modify(dfn[pos],x,op);
}
ull qry(int u,int v,ull w){//核心部分:链的合并
	node lc,rc;
	while(top[u]!=top[v]){
		if(dep[top[u]]>dep[top[v]]){
			lc=merge(segtree::query(dfn[top[u]],dfn[u]),lc);
			//细节1:树剖是从下到上跳的,故新的链是不断地并在前面得到的链的前端
			u=fa[top[u]];
		}
		else{
			rc=merge(segtree::query(dfn[top[v]],dfn[v]),rc);
			v=fa[top[v]];
		}
	}
	bool flg=1;
	if(dep[u]<dep[v])std::swap(u,v),std::swap(lc,rc),flg=0;
	std::swap(rc.lef0,rc.rig0),std::swap(rc.lef1,rc.rig1);
	//细节2:为了链的方向的统一方便合成最终链,需要把靠上(默认为v)的链左右颠倒
	node ans=merge(rc,merge(segtree::query(dfn[v],dfn[u]),lc));
	if(flg)std::swap(ans.lef0,ans.rig0),std::swap(ans.lef1,ans.rig1);
	//细节3:此时的链左端是v右端是u,故需要将答案链左右颠倒;但是如果u,v已经交换了就不用颠倒
	ull res=0;
	for(int i=K-1;~i;--i){
		ull l0=ans.lef0>>i&1,l1=ans.lef1>>i&1;
		if(l0)res|=1ull<<i;//可以那就尽量白嫖
		else if(l1&&(1ull<<i)<=w)res|=1ull<<i,w-=1ull<<i;//前文提到的贪心结论:能拿高位一定要拿
	}
	return res;
}
void adde(int u,int v){
	e[++etot]=(edge){v,h[u]};
	h[u]=etot;
}
signed main(){
	n=read();m=read();K=read();
	for(int i=1;i<=n;++i)OP[i]=read(),a[i]=uread();
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		adde(u,v);adde(v,u);
	}
	dfs1(1,1);
	dfs2(1,1);
	segtree::build();
	while(m--){
		int op=read(),u=read(),v=read();
		ull w=uread();
		if(op==1)printf("%llu\n",qry(u,v,w));
		else mdf(u,w,v);
	}
	return 0;
}

标签:OJ,int,题解,top,ull,LuoguP5354,lef1,op,mathrm
From: https://www.cnblogs.com/pokefunc/p/17137029.html

相关文章

  • HDOJ2097 Sky数
    Sky数TimeLimit:1000/1000MS(Java/Others)    MemoryLimit:32768/32768K(Java/Others)TotalSubmission(s):25391    AcceptedSubmission(s):14419P......
  • HDOJ2096 小明A+B
    小明A+BTimeLimit:1000/1000MS(Java/Others)    MemoryLimit:32768/32768K(Java/Others)TotalSubmission(s):46410    AcceptedSubmission(s):21882......
  • HDOJ2095 find your present (2)
    findyourpresent(2)TimeLimit:2000/1000MS(Java/Others)    MemoryLimit:32768/32768K(Java/Others)TotalSubmission(s):25477    AcceptedSubmiss......
  • HDOJ2094 产生冠军
    产生冠军TimeLimit:1000/1000MS(Java/Others)    MemoryLimit:32768/32768K(Java/Others)TotalSubmission(s):18681    AcceptedSubmission(s):8468......
  • HDOJ2006求奇数的乘积
    求奇数的乘积TimeLimit:2000/1000MS(Java/Others)    MemoryLimit:65536/32768K(Java/Others)TotalSubmission(s):103371    AcceptedSubmission(s):......
  • HDOJ2007 平方和与立方和
    平方和与立方和TimeLimit:2000/1000MS(Java/Others)    MemoryLimit:65536/32768K(Java/Others)TotalSubmission(s):185838    AcceptedSubmission(s)......
  • HDOJ2093 考试排名
    考试排名TimeLimit:1000/1000MS(Java/Others)    MemoryLimit:32768/32768K(Java/Others)TotalSubmission(s):16041    AcceptedSubmission(s):5604......
  • HDOJ2014 青年歌手大奖赛_评委会打分
    青年歌手大奖赛_评委会打分TimeLimit:2000/1000MS(Java/Others)    MemoryLimit:65536/32768K(Java/Others)TotalSubmission(s):95015    AcceptedSub......
  • HDOJ2016 数据的交换输出
    数据的交换输出TimeLimit:2000/1000MS(Java/Others)    MemoryLimit:65536/32768K(Java/Others)TotalSubmission(s):113095    AcceptedSubmission(s)......
  • HDOJ2015 偶数求和
    偶数求和TimeLimit:2000/1000MS(Java/Others)    MemoryLimit:65536/32768K(Java/Others)TotalSubmission(s):95563    AcceptedSubmission(s):40086......