[CSP-J 2022] T4 上升点列题解

前言

        欢迎大家来我的洛谷博客里阅读

        作为最后一道题,难度还没有第三题高。当年考场上蒟蒻的我一看到是第四题就跳了

 题目传送门

 题目大意

        有n个点,可以再添加k个点,求他们能构成的最长非严格递增序列

思路

        这道题有两种思路——动态规划和最短路,其中最短路又可以分为Floyd和spfa,这里主要讲一下spfa的做法,另外两种做法点这里:dp      floyd

SPFA思路

        输入中已经给出了每个点的坐标,所以我们可以很容易的求出任意两个点之间的距离(x2-x1+y2-y1-1)距离中减一的目的是把相邻两个点间的距离赋为0,也就是说只需要添加0个点就能到达。因为要求上升点列,所以在建图的时候只需要把当前点与xy值均大于等与当前的点之间连一条边。连边后循环遍历每一个端点,运用SPFA求最短路,即为从端点到每一个点需要添加的点的数量最后只需循环遍历每一个添加k个点后能到达的点,更新最长值

存点

        定义一个结构体,存储点的信息:x值,y值,点的编号

struct Point{//存储输入点的信息 
	long long x,y,id;
}a[505];

建图、存边

        我这里用的是链式前向星存图,建图的时候,依次把每个点都边里一下,找到两个递增的点,在他们之间连一条边,ba点的编号作为这条边端点的编号,把点的距离作为边的权值

struct Edge{//链式前向星存图 
	long long v,w,next;
}tu[N];
long long head[N],ce,vis[505],vist[505];//vis存储一个点是否走过的信息,vist存储一个点有没有遍历过的信息,vis用于SPFA找最短路,vist用于找端点 
inline void adde(long long u,long long v,long long w){//链式前向星添加边 
	tu[++ce].v=v;tu[ce].w=w;tu[ce].next=head[u];
	head[u]=ce;
}
for(long long i=1;i<=n;i++) 
	for(long long j=1;j<=n;j++){
		if(a[j].x>=a[i].x&&a[j].y>=a[i].y&&i!=j){//判断如果有两个点满足非严格递增,且不是这个点本身 
			adde(i,j,a[j].x-a[i].x+a[j].y-a[i].y-1);//加边 
		}
	}

SPFA

        直接用普通的SFPA是肯定不行的,这里需要用到SPFA的SLF优化。SPFA优化详解

inline void SPFA(long long x){//SPFA找最短路 
	for(long long i=1;i<=n;i++){//每次找最短路的时候都要初始化dis和vis的值 
		dis[i].w=inf,dis[i].id=i;
		vis[i]=0;
	}
	queue<long long> q;//队列 
	dis[x].w=0;
	q.push(x);
	vis[x]=1;
	vist[x]=1;//初始化 
	while(!q.empty()){
		long long u=q.front();//每次获取队首元素 
		q.pop();//将队首元素弹出 
		vis[u]=0;
		for(long long i=head[u];i;i=tu[i].next){//遍历每一条相连的边 
			long long v=tu[i].v,w=tu[i].w;
			if(dis[v].w>dis[u].w+w){//三角形不等式更新最小值 
				dis[v].w=dis[u].w+w;
				if(!vis[v]){//判断这个点有没有走过 
					q.push(v);//没有的话,更新为走过,并加入队列 
					vis[v]=1;
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(dis[i].w!=inf&&i!=x&&k>=dis[i].w){//判断添加上k个点后,端点能到哪些点 
			vist[i]=1;//把这些点标记为非端点 
		}
	}
	sort(dis+1,dis+n+1,cmp);
	for(long long i=1;i<=n;i++){
		if(k>=dis[i].w) ans=max(ans,a[dis[i].id].x-a[x].x+a[dis[i].id].y-a[x].y+1+k-dis[i].w);//如果加上k个点后能到达这个点,更新上升点列的最大值 
	}
}

主函数里的去重优化

        如果把每一个点都作为端点SPFA一次找最短路的话是肯定会超时的,所以,在每一次SPFA的过程中,我们都要记录一下有没有点是从当前端点出发无法到达的,如果有的话,在主函数里特判一下,把这个点作为下一次SPFA的端点

for(long long i=1;i<=n;i++){
    if(!vist[i]){//特判,每次找最短路只需要找端点,不加这一行的话会有两个点超时 
        SPFA(i);
	}
}

完整AC代码

#include<bits/stdc++.h>
using namespace std;
#define N 2500005
#define inf 0x3f3f3f3f
long long n,k;
struct Point{//存储输入点的信息 
	long long x,y,id;
}a[505];
struct Edge{//链式前向星存图 
	long long v,w,next;
}tu[N];
struct Ans{//这里把dis写成结构体,方便排序(其实不用排) 
	long long id,w;
}dis[505];
long long head[N],ce,vis[505],vist[505];//vis存储一个点是否走过的信息,vist存储一个点有没有遍历过的信息,vis用于SPFA找最短路,vist用于找端点 
inline void adde(long long u,long long v,long long w){//链式前向星添加边 
	tu[++ce].v=v;tu[ce].w=w;tu[ce].next=head[u];
	head[u]=ce;
}
inline bool cmp(Ans x,Ans b){//自定义sort排序 
	return x.w>b.w;
}
long long ans=-1;//把最长上升点列的长初始化为1 
inline void SPFA(long long x){//SPFA找最短路 
	for(long long i=1;i<=n;i++){//每次找最短路的时候都要初始化dis和vis的值 
		dis[i].w=inf,dis[i].id=i;
		vis[i]=0;
	}
	queue<long long> q;//队列 
	dis[x].w=0;
	q.push(x);
	vis[x]=1;
	vist[x]=1;//初始化 
	while(!q.empty()){
		long long u=q.front();//每次获取队首元素 
		q.pop();//将队首元素弹出 
		vis[u]=0;
		for(long long i=head[u];i;i=tu[i].next){//遍历每一条相连的边 
			long long v=tu[i].v,w=tu[i].w;
			if(dis[v].w>dis[u].w+w){//三角形不等式更新最小值 
				dis[v].w=dis[u].w+w;
				if(!vis[v]){//判断这个点有没有走过 
					q.push(v);//没有的话,更新为走过,并加入队列 
					vis[v]=1;
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(dis[i].w!=inf&&i!=x&&k>=dis[i].w){//判断添加上k个点后,端点能到哪些点 
			vist[i]=1;//把这些点标记为非端点 
		}
	}
	sort(dis+1,dis+n+1,cmp);//排序(感觉不用排,有没有大佬知道需不需要排,可以写在评论区) 
	for(long long i=1;i<=n;i++){
		if(k>=dis[i].w) ans=max(ans,a[dis[i].id].x-a[x].x+a[dis[i].id].y-a[x].y+1+k-dis[i].w);//如果加上k个点后能到达这个点,更新上升点列的最大值 
	}
}
int main(){
    ios::sync_with_stdio(0);//cin,cout提速 
	cin>>n>>k;
	for(long long i=1;i<=n;i++){
		cin>>a[i].x>>a[i].y;
		a[i].id=i;	
	}
	for(long long i=1;i<=n;i++) 
		for(long long j=1;j<=n;j++){
			if(a[j].x>=a[i].x&&a[j].y>=a[i].y&&i!=j){//判断如果有两个点满足非严格递增,且不是这个点本身 
				adde(i,j,a[j].x-a[i].x+a[j].y-a[i].y-1);//加边 
			}
		}
    for(long long i=1;i<=n;i++){
        if(!vist[i]){//特判,每次找最短路只需要找端点,不加这一行的话会有两个点超时 
            SPFA(i);
		}
    }
	cout<<ans<<endl;//输出 
	return 0;
}

SPFA没死

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值