P8817 [CSP-S 2022] 假期计划
P8817 [CSP-S 2022] 假期计划 题解
时隔一年后再来订正,终于理解了。降绿了才回来做。
题意简述:给一张无向图,点有点权,求\(\max{\{w_A+w_B+w_C+w_D\}}\),其中,\(A,B,C,D\)与一号结点构成一个环,环上相邻两点的距离\(\le k+1\)。
数据范围:\(5\le n\le 2500,1\le m\le 10000,0\le k\le 100\)。
Solution
- 一个直接的思路是暴力枚举一些点,在判断剩下的点是否合法。
显然\(n\)的范围允许我们枚举其中的两个点,一个直接的思路是枚举\(A,D\),再预处理出每个点在\(k\)次换乘能到达的点,这个集合的规模是\(O(n)\)的。
然后问题转化为两个点集\(S,T\),求两个点集中相邻的边的最大权值。这个问题无法在一个合适的时间复杂度内求解。
- 于是不妨试试枚举\(B,C\)两点,预处理出每个点在\(k\)次换乘能到达的点,且在\(k\)次换乘内可以到达 1 号点。
P.S.这其实是维护\(A,D\)所在的集合,\(A,D\)两点要可达 1 号点。
于是,我们只需每次找到点集\(S,T\)中,不相交的两个点中最大的权值。
但是,这个点集的规模是\(O(n)\)的,考虑优化。
观察性质,可以发现,如果考虑点\(A\),只用考虑是否与\(C,D\)重合。
于是,点集中的前三大的点中一定有一个合法决策。P.S.最坏情况是最大点和次大点都重合。
于是我们有一个std::set维护每个点可达的点中,又与 1 号点可达的元素中权值前三大的点。
每次找出合法决策的时间复杂度为\(O(|S|^2log^2|S|)\),\(|S|\)代表集合中的元素个数,这里\(|S|=3\),总的时间复杂度为\(O(n^2|S|^2log^2|S|)\)。
代码如下:
#include<bits/stdc++.h>
#define int long long
typedef long long ll;
using namespace std;
const int N=3e3+10,M=2e5+10,INF=0x3f3f3f3f3f3f3f3f;
int head[N],ver[M],nxt[M],tot=1;
void add(int x,int y){ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;}
int n,m,k,d[N][N];
ll w[N],ans;
set<pair<int,int>>S[N];
bool v[N];
void bfs(int s,int d[]){
queue<int>q;memset(v,0,sizeof(v));
for(int i=1;i<=n;i++)d[i]=INF;
d[s]=0;q.push(s);
while(q.size()){
int x=q.front();q.pop();
if(v[x])continue;
v[x]=1;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(d[y]>d[x]+1)d[y]=d[x]+1,q.push(y);
}
}
}
signed main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=2;i<=n;i++)scanf("%lld",&w[i]);
for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x);
for(int i=1;i<=n;i++)bfs(i,d[i]);
for(int i=2;i<=n;i++)for(int j=2;j<=n;j++){
if(i==j)continue;
if(d[i][j]<=k+1&&d[1][j]<=k+1)S[i].insert({w[j],j});
if(S[i].size()>3)S[i].erase(S[i].begin());
}
for(int b=2;b<=n;b++)for(int c=2;c<=n;c++){
if(d[b][c]>k+1||b==c)continue;
for(auto a:S[b]){
if(a.second==b||a.second==c)continue;
for(auto d:S[c]){
if(d.second==b||d.second==c||d.second==a.second)continue;
ans=max(ans,w[b]+w[c]+w[a.second]+w[d.second]);
}
}
}
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号