题解 [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 函数获得这个集合的元素数量, 而每个 ? 的位置都可能是 0 或 1, 于是答案就是 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 的附加信息从而被预处理器删除于是被编译器忽略, 从而没有关闭同步.

浙公网安备 33010602011771号