题解 [YLOI 2019] 棠梨煎雪

题解 [YLOI 2019] 棠梨煎雪, 去往 我的网志 阅读体验更佳.

指针, 不 using namespace std;, 喝 东方树叶, 听 银临, 吃 棠梨煎雪, 只要再割掉 root, 我就能成为本题出题人 一扶苏一女士 的复制人了.

problem:洛谷-P5522

岁岁花藻檐下共将棠梨煎雪
自总角至你我某日辗转天边
天淡天青 宿雨沾襟
一年一会信笺却只见寥寥数言
--银临《棠梨煎雪》

歌曲主要讲了主人公和朋友每年都互发一次 email, 每次想朋友的时候主人公都会做棠梨煎雪.

接下来是正经的题解, 注意按照我的习惯是 \(n\) 个长为 \(m\) 的弦, 但题目说的是 \(m\) 个长度为 \(n\) 的弦, 感性理解一下.

首先分析题意

\(n\) 条 email, 每条 email 是一个 0 1 串, 但由于 gmail 服务器爆炸导致一部分字符看不清了, 记为 ?.

询问是说如果朋友不小心发扬人类本质成为复读机, 在第 \([f,g]\) 条 email 有多少种可能, 即有多少可能是 \([f,g]\) 的 email 完全相同.

\(1\le n\le2^{17}\), \(1\le m\le30\).

第一眼

if u r good at bit operation, 可以发现 \(m\) 的范围恰好是 \(30\), 于是考虑使用两个整数处理一条 email.

第二眼

那么我们可以想到使用 \(v_{x,0}\) 记录第 \(x\) 条 email 每个 0 的位置放在对应的二进制位上; 同样使用 \(v_{x,1}\) 记录第 \(x\) 条 email 每个 1 的位置放在对应的二进制位上.

那么 ? 的位置就是剩下的, 即用全集 \(u=2^{m}-1\)\(v_{x,0}\cup v_{x,1}\) 取补集 \(u\setminus(v_{x,0}\cup v_{x,1})\).

知道了 ? 的集合之后, 我们可以使用 __builtin_popcountl 函数获得这个集合的元素数量, 而每个 ? 的位置都可能是 01, 于是答案就是 std::pow(2l,__builtin_popcountl((~s0&ultra)&(~s1&ultra))).

第三眼

于是一个区间的 \(v_{[f,g],0}\) \(v_{[f,g],1}\) 就可以用线段树或稀疏表维护.

线段树:
\(v_{[f,g],0}=v_{[f,\frac{f+g}{2}],0}|v_{[\frac{f+g}{2}+1,g],0}\)
\(v_{[f,g],1}=v_{[f,\frac{f+g}{2}],1}|v_{[\frac{f+g}{2}+1,g],1}\)

稀疏表:
\(v_{f,k,0}=v_{f,k-1,0}|v_{f+2^{k-1},k-1,0}\)
\(v_{f,k,1}=v_{f,k-1,1}|v_{f+2^{k-1},k-1,1}\)

第四眼

怎么还有修改啊, 扶苏姐姐毒瘤 !

还好是单点修改, 那么就用线段树吧, 显然直接重新记录这个叶子顶点的答案再一路往上 push_up 即可.

于是这是代码

#include<cmath>
#include<climits>
#include<cstdbool>
#include<cstddef>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<deque>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<tuple>
#include<vector>

using lf=double;
using us=size_t;
using ll=ptrdiff_t;
constexpr ll inf=0x3f3f3f3f3f3f3f3fl;
constexpr ll mod=0x3b800001l;
constexpr ll len=1l<<17;

typedef	struct	_tree	tree;
struct	_tree{
	tree *ld,*rd;
	ll f,g,v0,v1;
};

tree rt[len<<1],*tott=rt-1;	// 指针线段树二倍空间, 优于下标线段树的四倍空间

void	push_up(tree *x){	// 合并区间答案
	x->v0=x->ld->v0|x->rd->v0;
	x->v1=x->ld->v1|x->rd->v1;
}

auto	build(ll f,ll g)->tree*{
	tree *x=++tott;	
	x->f=f,x->g=g;
	x->v0=x->v1=0;
	if(f==g){
		std::string fusu;	// 扶苏姐姐最美
		std::cin>>fusu;
		ll k=1;
		for(char w:fusu){	// 记录每个位置是 0 1, 否则是 ?
			if(w=='0')
				x->v0|=k;
			else	if(w=='1')
				x->v1|=k;
			k<<=1;
		}	return x;
	}	x->ld=build(f,f+g>>1);
	x->rd=build((f+g>>1)+1,g);
	push_up(x);
	return x;
}

void	update(tree *x,ll h){
	if(x->f==x->g){
		x->v0=x->v1=0;
		std::string fusu;	// 扶苏姐姐最美
		std::cin>>fusu;
		ll k=1;
		for(char w:fusu){	// 重新记录
			if(w=='0')
				x->v0|=k;
			else	if(w=='1')
				x->v1|=k;
			k<<=1;
		}	return;
	}	if(h<=x->ld->g)
		update(x->ld,h);
	else	update(x->rd,h);
	push_up(x);
}

auto	query(tree *x,ll f,ll g)->std::tuple<ll,ll>{
	if(f<=x->f&&x->g<=g)
		return std::make_tuple(x->v0,x->v1);
	ll s0=0,s1=0;
	if(f<=x->ld->g){
		auto [l0,l1]=query(x->ld,f,g);
		s0|=l0,s1|=l1;
	}	if(x->rd->f<=g){
		auto [r0,r1]=query(x->rd,f,g);
		s0|=r0,s1|=r1;
	}	return std::make_tuple(s0,s1);
}

void	solve(void){
	ll m,n,q,ultra,fusu=0;
	std::cin>>m>>n>>q;
	ultra=(1l<<m)-1;
	build(1,n);
	while(q--){
		ll op;
		std::cin>>op;
		if(op==0){
			ll f,g;
			std::cin>>f>>g;
			auto [s0,s1]=query(rt,f,g);
			if(s0&s1)	// 有位置即是 0 也是 1, 显然不可能,于是有 0 种答案
				continue;
			fusu^=1l<<__builtin_popcountl((~s0&ultra)&(~s1&ultra));
		}else	if(op==1){
			ll h;
			std::cin>>h;
			update(rt,h);
		}else	std::cout<<"~\n";
	}	std::cout<<fusu<<"\n";
}

auto	main(void)->signed{
#ifdef	young_tea
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
#endif	//	i ak ioi & wf
	std::ios::sync_with_stdio(0);
	std::cin.tie(NULL),std::cout.tie(NULL);
	ll t=1;
//	std::cin>>t;		// 扶苏姐姐好, 扶苏姐姐不放多测
	while(t--)
		solve();
	return 0;
}

最后的最后, 警示后人

错误的关同步流

正确的关同步流

错误在于, 把 std::ios::sync_with_stdio(0); 放在 #endif 后面会导致 std::ios::sync_with_stdio(0); 被当成 #endif 的附加信息从而被预处理器删除于是被编译器忽略, 从而没有关闭同步.

posted @ 2025-11-19 21:06  young_tea  阅读(9)  评论(0)    收藏  举报