【题解】P8817 [CSP-S 2022] 假期计划(bfs,dfs)
【题解】P8817 [CSP-S 2022] 假期计划
此题作为 CSP-S 的 T1,可以说是相当有难度了。感觉 T1 和 T2 换了个位置。(雾)
我作为场外 VP 选手赛时此题只得了 95pts(洛谷民间数据),靠着自己乱搞 dfs+剪枝水过去了 19 个点。
这里记录一下这道题。
题目链接
题意概述
给定一张
思路分析
这个
为了方便说明这里自定义一些语言:
- 若
到 的最短路径 我们就称 到 可达。 - 方便起见我们认为点
到 本身不可达、
95pts 乱搞
先讲述一下我乱搞 95pts 的做法。
我当时看到这道题,,第一眼感觉正解应该想不到,然后就去想办法乱搞。
最暴力的做法是,对于四个点从
我们考虑如何优化。
首先由于每段路径的长度都
无权图上求最短路首先想到 bfs,这个显然可以枚举每个点然后预处理出来从每个点
然后预处理出来对于每个点
另外还可以预处理出从
考虑到 dfs 一定难逃剪枝,我们可以记录一下当前已经选过的点的点权和
需要注意的一点是,为了保证四个点不同,我们必须定义一个
然后这样做完,虽然看起来时间复杂度疑似
正解
我们发现在刚刚的暴力过程中,我们只要确定了前三个点,然后再只需要在第三个点
那么这可以给我们思考正解带来启发:由于要权值和最大,当我们固定了
同理,由于环的对称性,可以发现当我们确定了
将上述两点结合起来,给了我们一种思路:
我们只需要枚举
那么我们怎么做呢?难道直接枚举
emmm……仔细算一算复杂度发现最坏情况下其实趋近于
仔细思考这样一件事情:
我们对于
- 在
可达的点中; - 在被标记的点中(即
可达的点中) - 非
; - 权值最大。
实际上,我们可以把前两条放在一块来处理,即,我们通过 bfs 处理出来的集合
现在剩下最后两个条件。如果没有第三个条件。那么我们只需存
到这里其实大体思路已经梳理完了。现在考虑如何写。
如果我们直接分类讨论每个点会与哪个点冲突由于我们同时要考虑
代码实现
95pts 乱搞做法
//A
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=2505;
int a[maxn],dis[maxn][maxn],vis[maxn],book[maxn];
int ans=0;
int n,m,k,mx;
basic_string<int>edge[maxn],edge2[maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void bfs(int x)
{
queue<int>q;
q.push(x);
while(!q.empty())
{
int now=q.front();
q.pop();
for(int nxt:edge[now])
{
if(dis[x][nxt])continue;
dis[x][nxt]=dis[x][now]+1;
q.push(nxt);
}
}
}
void dfs(int x,int now,int sum)
{
if(sum+(4-now)*mx<ans)return ;//剪枝
if(now==3)
{
for(int y:edge2[x])
{
if(book[y]&&vis[y]==0)//如果这个点没选过且在 1 可达点集中则更新答案。
{
ans=max(ans,sum+a[y]);
}
}
return ;
}
for(int y:edge2[x])
{
if(vis[y])continue;
vis[y]++;
dfs(y,now+1,sum+a[y]);
vis[y]=0;
}
}
signed main()
{
n=read();m=read();k=read();k++;
for(int i=2;i<=n;i++)a[i]=read(),mx=max(mx,a[i]);
for(int i=1;i<=m;i++)
{
int u,v;
u=read();v=read();
edge[u]+=v;
edge[v]+=u;
}
for(int i=1;i<=n;i++)
{
bfs(i);//对每个点 bfs 预处理出来 dis
}
for(int i=1;i<=n;i++)
{
for(int j=2;j<=n;j++)
{
if(i==j)continue;
if(dis[i][j]&&dis[i][j]<=k)
{
edge2[i]+=j;//这份代码中的 edge2[i] 表示的就是 i 的可达点的集合。
}
}
}
for(int v:edge2[1])book[v]++;//标记所有的 1 可达的点。
dfs(1,0,0);
cout<<ans<<'\n';
return 0;
}
正解
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int maxn=2505;
int ok[maxn][maxn],dis[maxn][maxn],w[maxn];//ok[i][j] 表示 i,j 是否可达。
int n,m,k,ans;
vector<int>f[maxn];
basic_string<int>edge[maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int cmp(int a,int b){return w[a]>w[b];}
void bfs(int x)
{
queue<int>q;
q.push(x);
dis[x][x]=0;
while(!q.empty())
{
int now=q.front();
q.pop();
if(now!=x)
{
ok[x][now]++;
if(x!=1&&ok[1][now])//如果起点不为 1 且是 1 可达的点。
{
f[x].push_back(now);
sort(f[x].begin(),f[x].end(),cmp);//按边权从大到小排序。
if(f[x].size()>3)f[x].pop_back();//只保留前三大。
}
}
if(dis[x][now]>=k)continue;//如果当前 dis 已经大于等于 k 就没必要再用,因为不可能更新。
for(int nxt:edge[now])
{
if(dis[x][nxt]!=-1)continue;
dis[x][nxt]=dis[x][now]+1;
q.push(nxt);
}
}
}
signed main()
{
memset(dis,-1,sizeof(dis));
n=read();m=read();k=read();k++;
for(int i=2;i<=n;i++)w[i]=read();
for(int i=1;i<=m;i++)
{
int u,v;
u=read();v=read();
edge[u]+=v;
edge[v]+=u;
}
for(int i=1;i<=n;i++)bfs(i);//这里的 bfs 处理的是 f[x]
//暴力枚举每个 b,c,并对每对 b,c 枚举可能的 9 种答案。
for(int b=2;b<=n;b++)
{
for(int c=2;c<=n;c++)
{
if(!ok[b][c])continue ;//b 必须可达 c。
for(int a:f[b])
{
for(int d:f[c])
{
if(a!=c&&b!=d&&a!=d)//a 显然本身不可能与 b,d 不可能与 c 冲突。
{
ans=max(ans,w[a]+w[b]+w[c]+w[d]);//更新答案。
}
}
}
}
}
cout<<ans<<'\n';
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现