2021.5.23模拟赛赛后总结
其实这次考试题目还是很水的
2021.5.23模拟赛赛后总结
前言
首先这次考试题目的数据范围都很不对劲,动不动就 \(1e9 \ 1e8\) 的,看着吓人极了。
可是没想到,这题非常不讲武德
上来直接给 \(1e9\) 套个 \(\log\)
我全都防出去了啊,直接 给 \(1e9\) 的 \(\log\) 来个三次方,也卡过去了啊
可是没想到,他这 \(T2\) 又给我来一个 \(1e9\) ,我大意了啊,没有闪。
然后 \(RE\) 了。
其他题目也出现了一些傻逼错误
啊啊啊!无能狂怒
算了进正题吧。
T1
这题做法还是蛮多的。
能数的出来的就有 \(4\) 种。
做法 A
树上路径,树链剖分线段树,时间复杂度 \(O(N + Qlog2 N\)),空间复杂度 \(O(N)\)
这好极了啊,\(1e9\) 这玩意直接就非常离谱。
\(TLE+MLE\)
做法 B
可以发现我们要找 \(LCA\) 而且树的形态十分固定,也就是其实我们能直接算出我们需要的东西。
具体过程就是两个点不断地向上跳找父亲,直到到达根节点,并且记录下过程中经过的所有经过节点。
最后我们直接对比两个数组,他们的第一个重合部分就是 \(LCA\) ,然后直接把两个点到 \(LCA\) 的距离加起来就可以了。
首先两个点分别跳回根节点是 \(O(logn)\) 的,然后对比两个数组找 \(LCA\) 是 \(O(log^2n)\) 的,所以总复杂度就是 \(O(qlog^3n)\) 的。
没错拿了全场最劣解。
其实是可以优化的。
把他们经过的公共点存进栈里,然后弹出公共部分就行了。
直接掉两个 \(\log\) .
时间复杂度 \(O(QlogN)\),空间复杂度 $ O(logn)$.
非常不错。
放个考场代码
#define LawrenceSivan
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
const int maxn=1000;
#define INF 0x3f3f3f3f
int n,q;
int a[maxn],b[maxn];
int cnt1,cnt2;
inline int solve(int x,int y){
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
cnt1=cnt2=0;
bool flag=0;
int xx,yy;
if(x==1||y==1){
flag=1;
}
xx=x/2,yy=y/2;
for(;xx;xx/=2){
a[++cnt1]=xx;
}
for(;yy;yy/=2){
b[++cnt2]=yy;
}
/*cout<<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ";
cout<<endl;
for(re int i=1;i<=cnt1;i++){
cout<<a[i]<<" ";
}
cout<<endl;
for(re int i=1;i<=cnt2;i++){
cout<<b[i]<<" ";
}
cout<<endl;
cout<<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!";*/
int tx,ty;
for(re int i=1;i<=cnt1;i++){
for(re int j=1;j<=cnt2;j++){
if(a[i]==b[j]){
tx=i,ty=j;
goto loop;
}
if(a[i]==y){
tx=i,ty=0;
goto loop;
}
if(b[j]==x){
ty=j,tx=0;
goto loop;
}
}
}
loop:;
return flag==1?cnt1+cnt2:tx+ty;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
return x*f;
}
int main(){
#ifdef LawrenceSivan
freopen("rp.in","r",stdin);
freopen("rp.out","w",stdout);
#endif
n=read();q=read();
for(re int i=1,x,y;i<=q;i++){
x=read();y=read();
printf("%d\n",solve(x,y));
}
return 0;
}
做法 C
经过做法 \(B\) 的启发,我们可以意识到根本不需要让两个点同时跳啊,我们只需要让深度大的跳,直到编号相同,那么总步数就是答案。
时间复杂度 \(O(QlogN)\),空间复杂度 $ O(1)$.
做法 D
分析完全二叉树节点编号的二进制性质,发现一个节点的深度即为它的编号的二进制表示的位数,
如 \((10)_{10}= (1010)_2\) ,则 \(10\) 号节点深度为 \(4\);两个节点的最近公共祖先的编号就是两个节点的
编号的二进制表示的最长公共前缀,如 \((8)_{10} = (1000)_2\) ,\((5)_{10} = (101)_2\) 则它们的二进制表示
的最长公共前缀为 \((10)_2 = (2)_{10}\),它们的最近公共祖先就是 \(2\) 号节点。因此我们只需要把 \(X\) 和 \(Y\)
转换成二进制表示,然后求它们的最近公共前缀 \(Z\),答案即为 $ \left| X \right|$ \(+\) \(\left|Y \right|\) \(−\) \(2\left| Z \right|\)。时间复杂度 \(O(QlogN)\)
,空间复杂度 \(O(logN)\) .
代码是 @XYY1411的
#include <cstdio>
#include <cctype>
using namespace std;
int n, q, x, y, lx, ly, k;
template <typename T>
inline T qread() {
T n = 0;
char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) n = (n << 3) + (n << 1) + (c ^ 48), c = getchar();
return n;
}
inline int getlen(const int &n) {
int tmp = 1, s = 1;
while (n & (~s)) s = s << 1 | 1, ++tmp;
return tmp;
}
int main() {
#ifndef LOCAL
freopen("rp.in", "r", stdin);
freopen("rp.out", "w", stdout);
#endif
n = qread<int>(), q = qread<int>();
while (q--) {
x = qread<int>(), y = qread<int>();
lx = getlen(x), ly = getlen(y);
k = 0;
// while (k < lx && k < ly && ((x & (1 << (lx - 1 - k))) >> (lx - 1 - k)) == ((y & (1 << (ly - 1 - k))) >> (ly - 1 - k))) k++;
while (k < lx && k < ly && (bool(x & (1 << (lx - 1 - k)))) == (bool(y & (1 << (ly - 1 - k))))) k++;
printf("%d\n", lx - k + ly - k);
}
return 0;
}
T2
就是这个 ## 题,
在考场上想了一下以为节点所对区间要先预处理,于是开始爆肝区间,然后不知不觉开了 \(4\) 倍空间去记,然后后来一看这 \(nm\) 不就和线段树需要的空间一样了吗,反正都过不去了我还不如直接搞线段树,代码实现难度又低,又比较熟练。
于是又搞了 \(4\) 倍空间去搞线段树,然后就一顿操作非常娴熟然后一波敲下去直接一遍过编译一遍测样例然后都过了,然后啪的一下直接取消 \(freopen\) 的注释然后就直接关掉文件。
结果
\(nm\) 这玩意 能直接算!
脑子都 $*** $ 了吧!!
草,技不如人啊
然后有了区间然后累计区间内已经操作的值
具体的:
1.在确定结点区间时用到一个转二进制的小技巧,观察这棵二叉树会发现,结点的二进制可以体现它在线段树中的位置,如 \(7\) 的二进制是 \(111\) ,从二进制左数第二位开始看,\(1\) 则找右子,\(0\) 则找左子,\(111\) 即→右→右,那么就可以根据这个对区间二。
2.因为\(s_i\)是个等差数列所以可以用求和公式。
3.记得开 \(long\ long\) (参与求和公式的变量也要开 \(long\ long\))
//#define LawrenceSivan
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define re register
const int maxn=1e5+5;
int n,q,x,y;
ll sum(ll h,ll t){return t<h?0:(t+h)*(t-h+1)>>1;}
ll solve(int t,int x){
int i=0,l=1,r=n;
for(;(1<<i)<=x;++i);i-=2;
for(;~i;--i)if((x>>i)&1)l=1+((l+r)>>1);else r=(l+r)>>1;
return sum(l,min(t,r));
}
inline int read(){
int x=0, f=1;char ch=getchar();
while (!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
return x*f;
}
int main() {
#ifdef LawrenceSivan
freopen("aa.in", "r", stdin);
freopen("aa.out", "w", stdout);
#endif
n=read();q=read();
while(q--){scanf("%d%d",&x,&y);printf("%lld\n",solve(x,y));}
return 0;
}
T3
以为是个平衡树,但是年代太久远了早就不会写了。
于是开桶打了暴力。
突然脑子一抽意识到,这玩意不就是针对值域进行操作吗,根本不需要排序,只需要一大块一起处理就行了。
然后这玩意不就是权值线段树裸题吗?
敲一发就 \(A\) 了:
//#define LawrenceSivan
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
const int maxn=1e5+5;
#define INF 0x3f3f3f3f
#define ls rt<<1
#define rs rt<<1|1
int n,q;
char op[10];
struct SegmentTree{
int v,tag;
}st[maxn<<2];
inline void push_up(int rt){
st[rt].v=st[ls].v+st[rs].v;
}
inline void push_down(int rt){
if(st[rt].tag){
st[ls].tag=1;
st[rs].tag=1;
st[ls].v=0;
st[rs].v=0;
st[rt].tag=0;
}
}
void modify(int rt,int l,int r,int pos,int val){
if(l==r){
st[rt].v+=val;
return;
}
push_down(rt);
int mid=(l+r)>>1;
if(pos<=mid)modify(ls,l,mid,pos,val);
else modify(rs,mid+1,r,pos,val);
push_up(rt);
}
void change(int rt,int l,int r,int ql,int qr,int val){
if(ql>r||qr<l)return;
if(ql<=l&&qr>=r){
st[rt].v=val;
if(val==0){
st[rt].tag=1;
}
return;
}
push_down(rt);
int mid=(l+r)>>1;
change(ls,l,mid,ql,qr,val);
change(rs,mid+1,r,ql,qr,val);
push_up(rt);
}
int query(int rt,int l,int r,int ql,int qr){
if(ql>r||qr<l)return 0;
if(ql<=l&&qr>=r)return st[rt].v;
push_down(rt);
int mid=(l+r)>>1;
return query(ls,l,mid,ql,qr)+query(rs,mid+1,r,ql,qr);
}
int ask(int rt,int l,int r,int val){
if(l==r)return l;
push_down(rt);
int mid=(l+r)>>1;
if(val<=st[rs].v)ask(rs,mid+1,r,val);
else ask(ls,l,mid,val-st[rs].v);
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
return x*f;
}
int main(){
#ifdef LawrenceSivan
freopen("aa.in","r",stdin);
freopen("aa.out","w",stdout);
#endif
n=read();
while(cin>>op){
if(op[0]=='Q')break;
if(op[0]=='A'){
int x=read(),y=read();
modify(1,1,n,x,y);
continue;
}
if(op[0]=='C'){
int x=read(),y=read();
int ans=0;
ans=query(1,1,n,x,y);
printf("%d\n",ans);
continue;
}
if(op[0]=='R'){
int x=read();
int ans=0;
ans=query(1,1,n,1,x-1);
printf("%d\n",ans);
change(1,1,n,1,x-1,0);
continue;
}
if(op[0]=='L'){
int x=read();
int ans=0;
if(st[1].v<x)printf("No\n");
else printf("%d\n",ask(1,1,n,x));
continue;
}
}
return 0;
}
T4
待补充