寒假集训总结

本次寒假共学习了三个部分:

1 最短路

2 最小生成树

3 线段树

一,最短路

一共是三种主要方法:

1 弗洛伊德算法

是运用了dp的思想。初始化结束后,开始进行三重循环,每层循环从第一个节点开始遍历,直至遍历到第n个节点,设最外层循环当前节点为i,中间层循环的当前节点为j,内层循环的当前节点为k,且i≠j≠k。则以节点i为中介点,以节点j为起点,节点k为目标点,判断由起点j经由中介点i到达目标点k的代价值是否小于由起点j直接到目标点k的代价值,若小于,则将从起点j到目标点k的代价值d[j][k]更新为d[j][i]+d[i][k]。三重循环结束后,路径规划结束。

时间复杂度高,O(n^{3})。

for (ll k=1;k<=n;k++)
	{
		for (ll i=1;i<=n;i++)
		{
			for (ll j=1;j<=n;j++)
			{
				if (f[i][k]==0||f[k][j]==0) continue;
				f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
				if (f[i][j]==0) f[i][j]=f[i][k]+f[k][j];
			}
		}
	}

2 dijkstra算法

是运用了贪心的思想 ,可以使用一个优先队列来维护所有未被标记的节点,并以距离起点的距离作为优先级,每次选择优先级最高的节点作为当前节点。在遍历当前节点的邻居节点时,可以计算从起点到邻居节点的距离,并更新距离和路径信息。如果发现新的路径比之前记录的路径更短,则更新路径信息。

时间复杂度O(n^{2})。

void DJ(int s){
	for(int i = 1;i <= n; ++i) dis[i] = INF,vis[i] = false;
	priority_queue<PII,vector<PII>,greater<PII> > que;
	que.push({0,s});
	dis[s] = 0;
	while(!que.empty()){
	    int t = que.top().second;
	    que.pop();
	    if(vis[t]) continue;
	    vis[t] = true;
	    for(int i = 0,l = E[t].size();i < l; ++i) {
	        int j = E[t][i].first,w = E[t][i].second;
	        if(dis[j] > dis[t] + w){
	            dis[j] = dis[t] + w,que.push({dis[j],j});
	        }
	    }
	}
}

3 SPFA算法

是基于松弛操作的最短路算法,同时加入了队列优化。用队列来维护哪些点可能会需要松弛操作,这样就对必要的边都进行一次松弛操作,一直循环的进行这个操作,直到我们不能进行松弛操作为止。

时间复杂度O(km)。

void SPFA(int s){
	for(int i = 1;i <= n; ++i) 
		vis[i] = false,dis[i] = INF;
	queue<int> que;
	que.push(s);
	dis[s] = 0,vis[s] = true;
	while(!que.empty()){
		int t = que.front();
		que.pop();
		vis[t] = false;
		for(int i = 0,l = E[t].size();i < l; ++i) {
			int j = E[t][i].v;
			int k = E[t][i].w;
			if(dis[j] > dis[t] + k){
				dis[j] = dis[t] + k;
				if(!vis[j]){
					vis[j] = true;
					que.push(j);
				}
			}
		}
	}
}
二,最小生成树

概念:一个图中可能存在多条相连的边,我们一定可以从一个图中挑出一些边生成一棵树这仅仅是生成一棵树,还未满足最小,当图中每条边都存在权值时,这时候我们从图中生成一棵树(n - 1 条边),生成这棵树的总代价就是每条边的权重相加之和。

分为两种算法:

1  kruskal

Kruskal首先将所有的边按从小到大顺序排序,并将每一个点分属于n个独立的集合。然后按顺序枚举每一条边。如果这条边连接着两个不同的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合;如果这条边连接的两个点属于同一集合,就跳过。直到选取了n-1条边为止。可以用并查集实现集合的合并。

时间复杂度:O(mlogm)。 

sort (a+1,a+1+m,cmp);
	for (ll i=1;i<=m&&cnt<n-1;i++)
	{
		ll x=find(a[i].x);
		ll y=find(a[i].y);
		if (x!=y)
		{
		   cnt++;
		   f[x]=y;
		   ans+=a[i].z;
		}
	}
	if (cnt<n-1) cout<<"Keng Die!";
	else cout<<ans;

2 prim

每次从连通网中找出一个权值最小的边,这样的操作重复 N-1次,由N-1条权值最小的边组成的生成树就是最小生成树

时间复杂度:O(n^{2})。

三,线段树

主要有三个部分:

1 单点修改

先递归建树,

void tree(int p,int x,int y)//建立表示区间[x,y]的线段树,p为当前节点编号 
{
	f[p].x=x;//左端点赋值 
	f[p].y=y;//右端点赋值
	//为新节点表示的左右区间赋值 
	if (x<y)//还可以继续建立 
	{
	   tree(p*2,x,(x+y)/2);//递归建立左子树,左端点为x,右端点为(x+y)/2,将线段树分为两段 
	   tree(p*2+1,(x+y)/2+1,y);//递归建立右子树,左端点为(x+y)/2+1,右端点为y,将线段树分为两段 
	   f[p].v=f[p*2].v+f[p*2+1].v;//线段树的值为左右两段线段树之和 
	}
	else 
	{
	   f[p].v=a[x];//剩下的单独赋值为数列中对应的数 
	}
}

 再单点修改,

void add(int p,int k,int d)//把d加到包含k的区间上,改变区间的v值,p为当前讨论的节点编号  
{//1号节点(根节点)一定包含k,所以r从1号节点开始 
	f[p].v+=d;//对于每一个正在讨论的节点,将值加上d,类似于求前缀和 
	if (f[p].x==f[p].y) return;//左右端点重合,停止  
	if (f[2*p].x<=k&&f[p*2].y>=k)//当需要赋值的点在目前的左区间中 
	{
		add(p*2,k,d);//当前节点有左孩子,且左孩子表示的区间中包含了k 
	}
	if (f[2*p+1].x<=k&&f[p*2+1].y>=k)//当需要赋值的点在目前的左区间中 
	{
		add(p*2+1,k,d);//当前节点有右孩子,且右孩子表示的区间中包含了k 
	}
}

最后区间求和,

ll sum(int p,int x,int y)//对于待计算的区间[x,y],求该区间所有数字之和,p为当前讨论的节点编号
{
	if (y<f[p].x||x>f[p].y) return 0;//如果p表示的区间完全被[x,y]覆盖,就直接返回该区间的v值 
	if (x<=f[p].x&&f[p].y<=y) return f[p].v;//区间已经完全覆盖要求求和的区域,直接返回值 
	else //讨论[x,y]与p表示的区间部分相交的情况 
	{
	   ll flag=0;
	   flag+=sum(2*p,x,y);//讨论[x,y]与p的左孩子相交部分的和 
	   flag+=sum(2*p+1,x,y);//讨论[x,y]与p的右孩子相交部分的和 
	   return flag;
	}
}
2 区间修改

先递归建树,

void tree(int p,int x,int y)
{
	f[p].a=x;//左端点赋值 
	f[p].b=y;//右端点赋值
	//为新节点表示的左右区间赋值 
	f[p].v=f[p].lazy=0;//赋初值为0 
	if (x==y)
	{
	   f[p].v=a[x];//重合部分的值为原本数列上的数值 
	   return;
	}
	int mid=(x+y)/2;//二分 
	tree(p*2,x,mid);//递归建立左子树,左端点为x,右端点为(x+y)/2
	tree(p*2+1,mid+1,y);//递归建立右子树,左端点为(x+y)/2+1,右端点为y
	//将线段树分为两段  
	f[p].v=max(f[p*2].v,f[p*2+1].v);
}

再区间修改,

void Add(int x,int y,int k,int z)//将区间x到y的所有数字增加z,k为当前讨论到的节点 
{
	if(f[k].lazy!=0)PutDown(k);//遇到延迟标记,先进行下放 
	if(x<=f[k].a&&f[k].b<=y)//区间完全包含修改部分
	{
		f[k].lazy+=z;//标记节点要增加的值,也意味着它的子孙节点都要增加相同的值
		f[k].v+=z;//节点本身要增加的值,也意味着它的子孙节点都要增加相同的值
		return;
	}
	int mid=(f[k].a+f[k].b)/2;//二分 
	if(x<=mid&&y>=f[k*2].a)Add(x,y,k*2,z);//包含右区间,分到右边继续重复操作 
	if(y>mid&&x<=f[k*2+1].b)Add(x,y,k*2+1,z);//包含左区间 ,分到左边继续重复操作 
	f[k].v=max(f[k*2].v,f[k*2+1].v);//区间最大值等于左区间最大值和右区间最大值取最大 
}

还有区间求和 ,

int Ask(int x,int y,int k)//询问区间[x,y]的最大值,k为当前讨论到的节点 
{
	if(f[k].lazy!=0)PutDown(k);//将累积在点k上的Lazy值下放
	if(x<=f[k].a&&f[k].b<=y)return f[k].v;//区间完全包含查询部分,直接返回区间最大值 
	int Lmax=0,Rmax=0;//左区间最大值与右区间最大值 
	int mid=(f[k].a+f[k].b)/2;//二分 
	if(x<=mid&&y>=f[k*2].a)Lmax=Ask(x,y,k*2);//包含右区间,分到右边继续重复操作 
	if(y>mid&&x<=f[k*2+1].b)Rmax=Ask(x,y,k*2+1);//包含左区间 ,分到左边继续重复操作 
	return max(Lmax,Rmax);//区间最大值等于左区间最大值和右区间最大值取最大,返回之 
}

 最后还有延迟标记的下放,方便统一求和。

void PutDown(int k) //下放操作,将累积在点k上的Lazy值下放到它的儿子节点
{
	f[k*2].v+=f[k].lazy;//左节点的值加上上一层滞留的lazy值 
	f[k*2].lazy+=f[k].lazy;//更新右节点的lazy值 
	f[k*2+1].v+=f[k].lazy;//左节点的值加上上一层滞留的lazy值 
	f[k*2+1].lazy+=f[k].lazy;//更新右节点的lazy值 
	f[k].lazy=0;
}
3 动态开点

先区间修改,同时延迟标记,

void modify(ll i,ll l,ll r,ll a,ll b,ll k)
{
	if (lazy[i]) pushdown(i);
	if (a<=l && r<=b)
	{
		lazy[i]+=k;maxx[i]+=k;
		return ;
	}
	ll mid=(l+r)/2;
	if (a<=mid)
	{
		if (!ls[i])ls[i]=++t;
		modify(ls[i],l,mid,a,b,k);
	}
	if (b>mid)
	{
		if (!rs[i])rs[i]=++t;
		modify(rs[i],mid+1,r,a,b,k);
	}
	if (ls[i])maxx[i]=max(maxx[i],maxx[ls[i]]);
	if (rs[i])maxx[i]=max(maxx[i],maxx[rs[i]]);
}

 然后再区间求和,

ll getans(ll i,ll l,ll r,ll a,ll b)
{
	if (lazy[i])pushdown(i);
	if (a<=l&&r<=b)return maxx[i];
	ll mid=(l+r)/2,lmaxx=0,rmaxx=0;
	if (a<=mid) lmaxx=getans(ls[i],l,mid,a,b);
	if (b>mid) rmaxx=getans(rs[i],mid+1,r,a,b);
	return max(lmaxx,rmaxx);
}

最后是延迟标记的下放。

void pushdown(ll i)
{
	if (!ls[i])ls[i]=++t;
	if (!rs[i])rs[i]=++t;
	lazy[ls[i]]+=lazy[i];
	maxx[ls[i]]+=lazy[i];
	lazy[rs[i]]+=lazy[i];
	maxx[rs[i]]+=lazy[i];
	lazy[i]=0;
}

心态问题:

对待平常的练习考试要认真,不要过于依赖他人的帮助,也要自觉学习,努力才会有回报!

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值