2021/7/6——集训Day.1

昨天晚上还是正常失眠,跟隔壁寝室商量了卫生间值日是一个寝室一天,以男人的决斗方式我险胜,他们先。寝室只有四个人,以男人的决斗方式我输了,沦为了寝室长,自闭,早上我去洗漱还没人起床,我值过日还有两人每走,sr大佬还好心帮我把他们踩脏的地方拖干净了,针不戳。早上由于hyp回去拿了个东西,我和sr在外面等的时候还被围观了,梨谱 … … …… ……而我自己还忘了带水杯,只能买了瓶框钱水。
早上过来就直接跟榜,开始写一道并查集的题:给出一个长度为 100000 100000 100000 的序列,每个数字不超过 1 0 9 10^9 109 ,再给每个时间摧毁序列中第几个数,然后输出 n n n 次,每次输出没有被摧毁的数组成的最大的连续子序列。

#include<bits/stdc++.h>
using namespace std;
#define N 100010
bool flag[N];long long ans[N],fa[N],sz[N],maxx;int n,fh,a[N],t[N];
int get_father(int x)
{
	if(x==fa[x])return x;
	return fa[x]=get_father(fa[x]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){scanf("%d",&a[i]);fa[i]=i;}
	for(int i=1;i<=n;i++){scanf("%d",&t[i]);}
	for(int i=n;i>1;i--)
	{
		fh=t[i];flag[fh]=1;//复活
		if(flag[fh-1]==1){sz[fh]+=sz[get_father(fh-1)];fa[get_father(fh-1)]=fh;}
		if(flag[fh+1]==1){sz[fh]+=sz[get_father(fh+1)];fa[get_father(fh+1)]=fh;}
		sz[fh]+=a[fh];maxx=max(maxx,sz[fh]);ans[i]=maxx;
	}
	for(int i=2;i<=n;i++)printf("%lld\n",ans[i]);printf("0");
	return 0;
}

解:倒着做,不要从一开始把序列中的数毁掉,而是把数一个一个复活,然后并查集维护。
早上写出来了,但是有一个小细节没搞好,然后课间改了出来。


上午是jjh金牌学长讲课,讲的是一些数据结构,并查集 + + + 树状数组 + + + S T ST ST 表 + + + 线段树,涉及到些目前我认为的比较高级的操作。
感觉算是听懂了大半吧,不能说完全明白,但是绝对受益匪浅。
比方说zkw线段树,摒弃了传统线段树的从根到叶,zkw线段树是从叶子到根,但是有些传统线段树的操作在zkw线段树中是难以实现的。感觉实用程度不如我的学长zyz发明的zyz线段树。
还有并查集中的 K r u s k a l Kruskal Kruskal 重构树,感觉好高级,用法和道理都懂,就是不会码。
最梨谱的是最后讲的“历史最小值”,直接去世。
S T ST ST 表我还是不会自己手写 … … …… ……自闭了,辛亏不是今天的重点内容,不然就亏大发了。
说起来课间还嫖了jjh学长带的《纪念品》——来自清华大学的小书签和胶带。
中午为了不被卷死,就留在了机房不敢回寝室,真自闭啊,吃饭的时候还因为头晕没站稳把汤撒到桌子上很多,差点溅到lyn大佬的外套上!
但是中午刚回来就吃了好多jjh学长和他女朋友的狗粮,太自闭了,根本学不进去啊!!!(还好jjh学长一会儿就睡着了,然后我就开始写博客总结)
但说起来还是hyp大佬强啊(给hyp大佬的真实姓名和头像手动打码)
2021/7/6——集训Day.1
等到午休时间结束,zwj老师说要在发的《学员证》上写名字,然后由于我昨天不小心洗脸把里面的卡片弄湿了没管,想掏出来写名字的时候纸断在了里面,太自闭了。


总结下具体的知识点吧:
首先是启发式合并

如果有两个并查集块要合并,对于每个节点维护出子树的大小,然后让小的成为大的的儿子,可以把查询根节点的复杂度降低为为 O(log n),这是数据结构,所以具体我就不证明了,以知道+会用为主。

还有就是路径压缩

将所有孩子的父亲直接连到祖先身上,最优情况下树可以变成菊花图,而在一般数据范围下查询连通性时时间复杂度应该是一个3~4的小常数(jjh学长是这样讲的,我以前一直以为是O(1)的)

然后是 K r u s k a l Kruskal Kruskal 重构树,以权值从小到大或从大到小连出一个并查集,由于并查集主要是针对集合,所以这样做也没有太大关系。当时课上给的例题是:

给n个点,m个有权值的边,然后每次询问时给出一个a值,从x点开始,一开始长度小于a的边可以直接跳过,然后求出跑到1号节点所经过的路程的长度。

P4768 [NOI2018] 归程
题意大概就是这样,我当时理解的这就跟飞行棋一样——一开始掷骰子没小于 4 4 4 的话不能出家门,等出了家门就可以随便搞了。
然后一开始就无从下手。正解大概就是先跑个 D i j k s t r a Dijkstra Dijkstra ,然后用 K r u s k a l Kruskal Kruskal 重构树维护,这样就可以直接跳过权值小于 a a a 的边了,再用倍增优化。(口胡出来大概就是这样了吧,但是对于 K r u s k a l Kruskal Kruskal 重构树的代码和倍增的细节还是不太清楚,大概就是直接加上点权大于 a a a 的限制在树上倍增即可),代码部分先跳过 … … …… ……
树状数组中包含了我昨天写的那个例题,但是被一笔改过了。主要还是我太菜,昨天写了那么长的时间才搞出来,真乾啊!

再往下就是 S T ST ST 表

只能询问不能修改,这个算法如果想要活下来,询问的复杂度必然是极低的。 O(n log n) 的时间内足够完成 ST 表的构造。而对于查询最大值一类的问题,可以拆成几个子区间,单次查询复杂度为O(1)。在求 LCA 的问题中,可以不用树链剖分或者倍增,而是用 ST 表+欧拉序(跟dfs序相似,我大概说下吧)进行复杂度为O(1)的查询,就很神奇。

对于一个树2021/7/6——集训Day.1
我们左儿子优先 d f s dfs dfs 序为 1    2    3    4    5 1\; 2\; 3 \;4 \;5 12345
而欧拉序为 1    2    3    2    4    2    1    5    1 1 \;2 \;3 \;2 \;4 \;2 \;1 \;5 \;1 123242151(大概就是这样子,不管是从上到下还是从下到上的时候,只要是经过了就让它进去)
zyz学长给了个求欧拉序的代码(出于对版权的尊重,码风我就不改了)

void dfs(int x,int F)
{
	stb[id[x]=++dfs_clock][0]=x;
	Graph(x){
		if(y==F)continue;
		dep[y]=dep[x]+1;
		dfs(y,x);
		stb[++dfs_clock][0]=x;
	}
}

具体咋实现我也不会,上午全程就光听学长口胡了,太细的地方还要自己抠抠才好
再往下是线段树区间修改区间查询之类的东西,懒标记我还是会下的。
zkw线段树

emmmm……感觉应用前景不是很广泛,前文也说过是做什么用的,我没太学会,有这功夫还不如想想zyz线段树,zyz学长yyds!!!(破音)

线段树求对于一个区间的最大子区间和:

维护每个区间的最大子区间和,从左端点出发的最大子区间,从右端点出发的最大子区间,这是很容易理解的,当两个区间合并时,我们在左区间的最大子区间,右区间的最大子区间,和两区间中间连着的部分(分别为靠近另一区间为端点的最大子区间的和),三者中取max即为该区间的最大子区间,然后以此类推往上传递

历史最小值问题:

没太听懂


然后翻译了个题面,开始码代码。
突然又过来统一解答了些上午存在的问题,历史最小值还是不懂,太自闭了。
第一个题

给出n-1个有向边,由儿子指向父亲,q次操作,可能是把某一点标记,也可能是询问某一点的最近被标记祖先(一个点本身也算是自己的祖先)

P4092 [HEOI2016/TJOI2016]树
自闭了,本来应该是个并查集的题,但是他们都是用的 d f s dfs dfs 水过去的,我当然不能搞特殊!
练成无向边,然后 d f s dfs dfs 遍历,给每个点都标记,如果删标记把一个点上的标记删完,就合并,总之很神奇,洛谷上也 A A A 掉了。(一开始邻接表没开二倍错了一发)

#include<bits/stdc++.h>
using namespace std;
#define N 100010
struct eo{int y,nxt;}e[N<<1];
int f[N],shen[N],q,n,tot,a,hd[N],b;char flag[2];
void lian(int x,int y){e[++tot].y=y;e[tot].nxt=hd[x];hd[x]=tot;}
void dfs(int x,int y)
{
	for(int i=hd[x];i;i=e[i].nxt)
	{
		if(e[i].y==y)continue;
		shen[e[i].y]=shen[x]+1;dfs(e[i].y,x);
	} 
}
void xiugai(int x,int y)
{
	for(int i=hd[x];i;i=e[i].nxt)
	{
		if(shen[e[i].y]<shen[x])continue;
		if(shen[f[e[i].y]]<shen[f[x]]){f[e[i].y]=f[x];xiugai(e[i].y,x);}
	}
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<n;i++){f[i]=1;scanf("%d%d",&a,&b);lian(a,b),lian(b,a);}
	shen[1]=1;dfs(1,0);f[n]=1;
	while(q--)
	{
		scanf("%s%d",flag,&a);
		if(flag[0]=='C'){f[a]=a;xiugai(a,a);}
		else printf("%d\n",f[a]);
	}
	return 0;
}

然后把一个之前写过的题粘到集训的临时 o j oj oj 中提交,啧啧啧
然后开始看[HAOI2015]树上操作
然后看到吃饭也不想码代码,吃饭的时候myf大佬说这是树链剖分,所以我又不想写了
开始看P2048 [NOI2010] 超级钢琴,并且现学 S T ST ST 表

ST算法利用倍增的思想,在O(N logN)的复杂度下进行预处理,然后通过O(1)的复杂度就可以查询到任意区间中的最大值,以倍增的方式找到一个长度lenth,使得lenth>=(l+r)/2,感觉就跟线段树差不了多少啊,只不过可能会覆盖同一小块儿区间多次而已啊。不过板子差的还挺多的,而且不能修改……

还在看书的时候发现自己被机惨了
然后运用了电脑自带的画图反%%%回去
2021/7/6——集训Day.1
然后上个厕所呼吸下新鲜空气继续学习
看了看晋文公世子jsy大佬的代码,有个很酷的操作(我一开始没注意到树上有,问了才知道):

bool operator <(const node &x,const node &y)
{
	return sum[x.t]-sum[x.s-1]<sum[y.t]-sum[y.s-1];
}

原来是结构体的堆 … … …… ……太神奇了,然后就看着jsy的代码和《算阶》写完了

#include<bits/stdc++.h>
using namespace std;
#define N 500010
int f[N][23],n,k,zuo,you,a;
long long qzh[N],ans;
struct node{int s,l,r,t;}st1,st2;
bool operator <(const node &x,const node &y){return qzh[x.t]-qzh[x.s-1]<qzh[y.t]-qzh[y.s-1];}
priority_queue<node> q;
void ycl()
{
	for(int i=1;i<=n;i++)f[i][0]=i;
	int t=log(n)/log(2)+1;
	for(int j=1;j<t;j++)for(int i=1;i<=n-(1<<j)+1;i++)
	{
		if(qzh[f[i][j-1]]>qzh[f[i+(1<<(j-1))][j-1]])f[i][j]=f[i][j-1];
		else f[i][j]=f[i+(1<<(j-1))][j-1];
	}
}
int xw(int l,int r)
{
	int k=log(r-l+1)/log(2);
	if(qzh[f[l][k]]>qzh[f[r-(1<<k)+1][k]])return f[l][k];
	return f[r-(1<<k)+1][k];
//	return max(f[r-(1<<k)+1][k],f[l][k]);
}
int main()
{
	scanf("%d%d%d%d",&n,&k,&zuo,&you);
	for(int i=1;i<=n;i++){scanf("%d",&a);qzh[i]=qzh[i-1]+a;}
	ycl();
	for(int i=1;i<=n-zuo+1;i++)
	{
		st1.s=i;st1.l=i+zuo-1;st1.r=min(i+you-1,n);
		st1.t=xw(st1.l,st1.r);q.push(st1);
	}
	while(k--)
	{
		st1=q.top();q.pop();
		ans=ans+qzh[st1.t]-qzh[st1.s-1];st2.s=st1.s;
		if(st1.l!=st1.t)
		{
			st2.t=xw(st1.l,st1.t-1);
			st2.l=st1.l;st2.r=st1.t-1;q.push(st2);
		} 
		if(st1.r!=st1.t)
		{
			st2.t=xw(st1.t+1,st1.r);
			st2.l=st1.t+1;st2.r=st1.r;q.push(st2);
		}
	}
	printf("%lld",ans);
	return 0;
 } 

用堆维护一个四元组(四元组是啥?我也不懂,反正jjh学长是这样写的,我看就是个结构体),然后取出结构体的时候就再拆拆,得到 S T ST ST 表再放进去。半懂不懂吧,真难啊,嘤。
2021/7/6——集训Day.1
《 我 与 出 题 人 》 《我与出题人》 《我与出题人》
然后开始复习树链剖分,之所以把“复习”打成斜体是因为我一开始就妹有学会,太离谱了。对于[HAOI2015]树上操作这个题,其实原来老师布置的作业里有,当时我就不会写,真自闭啊 … … …… ……

《算阶》上没有找到,看别人博客大概就是:找重边?然后剖出来链处理?(不要看这一句话,会误人子弟的)。最后也没搞明白,然后差不多该回寝室了,就这样草草结束吧,等明天老师讲

上一篇:Python常见数据类型及使用方法


下一篇:Android ble蓝牙问题