海亮寄 7.4

前言

业精于勤荒于嬉,行成于思而毁于随

正文(加餐)

最短路选讲,这是要 \(DAY^{-1}\) 的节奏

浅贴一个课件

关键词:多源最短路、前置转化、临界分割

再浅贴一下题单(不全)

是被小小登薄纱的一天

T1

感觉如果不听讲座的话,题意理解能卡我半个点

题意

给定 \(n\) 个元素,你需要构造若干组子集选取以及权值钦定的方案使得满足如下限制

  • 编号为 \(1\) 的元素存在于所有的方案中,编号为 \(n\) 的元素不存在于任意一个方案中

  • 给定 \(m\) 条约束 \((u,v,w)\),表示 \(u,v\) 中恰选取一个元素的方案的权值和不超过 \(w\)

其中权值是你需要构造的自然数,要求最大化权值和

题解

容易发现,题目描述很图论,于是乎考虑图论建模

在图上,一次方案的选取相当于选中若干个联通块,然后使得与选中联通块相邻的所有边的边权自减 \(1\)

图结构并不好看,考虑同时包含 \(1,n\) 两个节点的链结构

贪心结论:每次方案的选取仅选取一个前缀,权值为选与不选临界边的边权

推广到图上,相当于每找到一条由 \(1\)\(n\) 的路径,就给答案造成了一个上界的约束

所以,整个图的答案上界就是 \(1\)\(n\) 的最短路

(特别地,如果 \(1,n\) 不连通,则答案为 inf

答案显然是可以够到上界的,一种比较 naive 的构造方法是,对 dis 排序扫描线,每次方案选取前缀即可

代码

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=105,M=5e3+5,INF=9e18;
int n,m,head[N],tot,dis[N];bool vis[N];
struct Edge{int to,nxt,val;}e[M<<1];
struct node{
    int id,dis;
    friend bool operator < (node s,node t){
        return s.dis>t.dis;
    }
};
priority_queue<node> q;
struct NODE{
    int pos,val;
    friend bool operator < (NODE s,NODE t){
        return s.val<t.val;
    }
}a[N];
int p[N];
inline void add(int u,int v,int w){
    e[++tot]={v,head[u],w};head[u]=tot;
    return;
}
inline void dijkstra(){
	for(int i=1;i<=n;i++)dis[i]=INF;
    dis[1]=0;q.push({1,0});
    while(!q.empty()){
    	int u=q.top().id;q.pop();
    	if(vis[u])continue;
		vis[u]=true;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to,w=e[i].val;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				if(!vis[v])q.push({v,dis[v]});
			}
		}
	}
	return;
}
signed main(){ 
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v,w;cin>>u>>v>>w;
        add(u,v,w),add(v,u,w);
    }
    dijkstra();
    if(dis[n]>=INF){cout<<"inf\n";return 0;}
    cout<<dis[n]<<' ';
    for(int i=2;i<=n;i++)a[i-1]={i,dis[i]};
    sort(a+1,a+n);
    for(int i=1;i<=n-1;i++)
        if(a[i].pos==n){
            cout<<i<<'\n';
            break;
        }
    p[1]=1;
    for(int i=1;i<=n-1;i++){
        for(int j=1;j<=n;j++)cout<<p[j];
        cout<<' '<<dis[a[i].pos]-dis[a[i-1].pos]<<'\n';
        if(a[i].pos==n)break;
        p[a[i].pos]=1;
    }
    return 0;
}

T2

疑似一道远古模拟赛题目,且场上场下都不会做

但可能难度只有联赛 T2 难度吧……

题意

给定 \(n\) 个点 \(m\) 条边的带边权连通无向图,其中有 \(p\) 个点是特殊点

对于每个特殊点,求出它到离它最近的其它特殊点的距离

题解

经典多源最短路问题

我们考察最近的两个特殊点 \(u,v\),其最短路径上非 \(u,v\) 的任一结点 \(i\)满足性质:

  • 是非特殊殊点

  • 与该结点最近的特殊点一定是 \(u,v\) 中的一个,简记为 \(f_i = u/v\)

依照上一道题目的一个重要思想,我们考虑分析临界边

即考察一组相邻的结点 \(x,y\),使得 \(f_x=u \land f_y=v\)

\(f\) 数组可以简单用多源最短路跑出来

然后我们发现,只要存在一组上述的 \(x,y\),就一定会对 \(u,v\) 造成贡献

所以直接枚举边集即可

代码

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,INF=4e18;
int n,m,p,x[N];
int head[N],tot,dis[N],col[N],ans[N];
bool vis[N];
struct Edge{int to,nxt,val;}e[N<<1];
struct node{
    int id,dis;
    friend bool operator < (node s,node t){
        return s.dis>t.dis;
    }
};
priority_queue<node> q;
inline void add(int u,int v,int w){
    e[++tot]={v,head[u],w};head[u]=tot;
    return;
}
inline void dijkstra(){
    for(int i=1;i<=n;i++)dis[i]=INF;
    for(int i=1;i<=p;i++)dis[x[i]]=0;
    for(int i=1;i<=p;i++)col[x[i]]=x[i];
    // for(int i=1;i<=n;i++)cerr<<dis[i]<<' ';
    // cerr<<'\n';
    // for(int i=1;i<=n;i++)cerr<<col[i]<<' ';
    // cerr<<'\n';
    for(int i=1;i<=p;i++)q.push({x[i],0});
    while(!q.empty()){
        int u=q.top().id;q.pop();
        if(vis[u])continue;
        vis[u]=true;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to,w=e[i].val;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;col[v]=col[u];
                if(!vis[v])q.push({v,dis[v]});
            }
        }
    }
    return;
}
signed main(){
    // freopen("in.txt","r",stdin);
    // freopen("out.txt","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>p;
    for(int i=1;i<=p;i++)cin>>x[i];
    for(int i=1;i<=m;i++){
        int u,v,w;cin>>u>>v>>w;
        add(u,v,w),add(v,u,w);
    }
    dijkstra();
    // for(int i=1;i<=n;i++)cerr<<dis[i]<<' ';
    // cerr<<'\n';
    // for(int i=1;i<=n;i++)cerr<<col[i]<<' ';
    for(int i=1;i<=p;i++)ans[x[i]]=INF;
    for(int u=1;u<=n;u++){
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to,w=e[i].val;
            if(col[u]==col[v])continue;
            ans[col[u]]=min(ans[col[u]],dis[u]+dis[v]+w);
            ans[col[v]]=min(ans[col[v]],dis[u]+dis[v]+w);
        }
    }
    for(int i=1;i<=p;i++)cout<<ans[x[i]]<<' ';
    cout<<'\n';
    return 0;
}

T3

神马?你告诉我——我卡了一个点的题目墨鱼十分钟就 A 了???

还好还好,墨鱼比我老,没有被偏序

题意

\(n\) 个点 \(m\) 条边的带权无向图,图有 \(k\) 个出口,目标是从起始点走到任意一个出口。每次处于一个节点时,周围会有 \(d\) 个道路会被封闭,问最坏情况下走出去的耗时,图保证连通

题解

直接做不好做,因为我们难以决策被封锁的道路,所以考虑正难则反

我们发现,如果一个结点到任意一个出口的答案构造方案是 \(d+1\) 短路

所以,我们需要写一个多源 \(d+1\) 短路(来自 \(k\) 短路的压迫感)

实际上不需要搞那个黑题,因为 \(d\) 的数据范围很小,直接对每一个结点开优先队列动态维护最小的 \(d+1\) 个贡献即可

代码

代码能力真的是还不如小登

dijkstra 内部优先队列的维护出现了一些问题(可能没睡醒?)

一个不小心维护成了前 \(d\) 个贡献给结点的最大值捏(问题是还把样例草过去了)

优先队列的维护是这样的

  • size>=d,仅插入不弹出

  • size = d+1,插入之后弹出一个

  • S[v].size()==d+1&&dis[v]>S[v].top(),更新 dis[v] 并把 \(v\) 丢进最短路径的优先队列中

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,M=1e6+5,INF=4e18;
int n,m,k,d,p[N],head[N],tot,dis[N],cnt[N];bool vis[N];
struct Edge{int to,nxt,val;}e[M<<1];
priority_queue<int> S[N];
struct node{
    int id,dis;
    friend bool operator < (node s,node t){
        return s.dis>t.dis;
    }
};
priority_queue<node> q;
inline void add(int u,int v,int w){
    e[++tot]={v,head[u],w};head[u]=tot;
    return;
}
inline void dijkstra(){
    for(int i=0;i<=n;i++)dis[i]=INF;
    for(int i=1;i<=k;i++)dis[p[i]]=0,cnt[p[i]]=d;
    for(int i=1;i<=k;i++)q.push({p[i],0});
    while(!q.empty()){
        int u=q.top().id;q.pop();
        if(vis[u])continue;
        vis[u]=true;
        for(int i=head[u];~i;i=e[i].nxt){
            int v=e[i].to,w=e[i].val;
            if((int)(S[v].size())<=d)S[v].push(dis[u]+w);
            else S[v].push(dis[u]+w),S[v].pop();
            if((int)(S[v].size())==d+1&&dis[v]>S[v].top()){
                dis[v]=S[v].top();
                q.push({v,dis[v]});
            }
        }
    }
    return;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>k>>d;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++){
        int u,v,w;cin>>u>>v>>w;
        add(u,v,w),add(v,u,w);
    }
    for(int i=1;i<=k;i++)cin>>p[i];
    dijkstra();
    cout<<(dis[0]>=INF?-1:dis[0])<<'\n';
    return 0;
}

T4

理解错了题意,荒废了一个半点

拒绝摆烂,从我做起!mc 害人,gou 都不玩!

题意

给定一个网格图构成的迷宫(障碍、空地)以及起终点,四联通邻域内方向行动代价为 \(1\)

然后可以丢置传送门,丢置的方式是在某一个方向上不断飞行直到边界或者障碍物

当地图上恰好存在两个传送门时,可以进行代价为 \(1\) 的跃迁

(就是上面这个地方一个不小心理解成了末影珍珠)

求最小代价

题解

图论建模

容易发现,可以预处理出任意一个格子四个方向丢置传送门的终点(即四个方向上距离该格子最近的障碍物或边界)

考察传送门的加速形如

花费四方向上最少的一段的代价,传送到任意一个其它传送门的位置

直接依照上述分析建模即可

代码

代码实现是有难度的,写的有点冗长了

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5,M=8e6+5,INF=0x3f3f3f3f;
const int dx[]={0,0,1,0,-1},dy[]={0,1,0,-1,0};
int n,m,head[N*N],tot,S,T;char a[N][N];
int L[N][N],R[N][N],U[N][N],D[N][N];
int dis[N*N];bool vis[N*N];
struct Edge{int to,nxt,val;}e[M];
struct node{
    int id,dis;
    friend bool operator < (node s,node t){
        return s.dis>t.dis;
    }
};
priority_queue<node> q;
inline void add(int u,int v,int w){
    e[++tot]={v,head[u],w};head[u]=tot;
    return;
}
inline int id(int x,int y){return (x-1)*m+y;}
inline bool inbound(int x,int y){return x>=1&&x<=n&&y>=1&&y<=m;}
inline void build(int x,int y){
    if(a[x][y]=='#')return;
    for(int i=1;i<=4;i++){
        int nx=x+dx[i],ny=y+dy[i];
        if(inbound(nx,ny)&&a[nx][ny]!='#'){
            int u=id(x,y),v=id(nx,ny);add(u,v,1);
        }
    }
    return;
}
inline void init(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            L[i][j]=(a[i][j]=='#'?j:L[i][j-1]);
    for(int i=1;i<=n;i++){
        R[i][m+1]=m+1;
        for(int j=m;j>=1;j--)
            R[i][j]=(a[i][j]=='#'?j:R[i][j+1]);
    }
    for(int j=1;j<=m;j++)
        for(int i=1;i<=n;i++)
            U[i][j]=(a[i][j]=='#'?i:U[i-1][j]);
    for(int j=1;j<=m;j++){
        D[n+1][j]=n+1;
        for(int i=n;i>=1;i--)
            D[i][j]=(a[i][j]=='#'?i:D[i+1][j]);
    }
    return;
}
inline void work(int x,int y){
    if(a[x][y]=='#')return;
    int u=id(x,y),_L=y-L[x][y],_R=R[x][y]-y,_U=x-U[x][y],_D=D[x][y]-x;
    int d=min({_L,_R,_U,_D});
    add(u,id(x,L[x][y]+1),d);
    add(u,id(x,R[x][y]-1),d);
    add(u,id(U[x][y]+1,y),d);
    add(u,id(D[x][y]-1,y),d);
    return;
}
inline void dijkstra(){
    for(int i=1;i<=n*m;i++)dis[i]=INF;
    dis[S]=0;q.push({S,0});
    while(!q.empty()){
        int u=q.top().id;q.pop();
        if(vis[u])continue;
        vis[u]=true;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to,w=e[i].val;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                if(!vis[v])q.push({v,dis[v]});
            }
        }
    }
    return;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>(a[i]+1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            build(i,j);
    init();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            work(i,j);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(a[i][j]=='S')S=id(i,j);
            if(a[i][j]=='C')T=id(i,j);
        }
    dijkstra();
    cout<<dis[T]<<'\n';
    return 0;
}

T5

疑似简单题

题意

一个人在有向图的 \(1\) 结点,他要去 \(n\) 结点。有向边形如 \((u,v,w)\),表示走过这条边需要花 \(w\) 元。这个人一开始有 \(p\) 元,到了一个点 \(u\),他可以进行若干次演出,每次演出收获 \(w_u\) 元。问到达 \(n\) 的最小演出次数,若无解输出 \(-1\)

题解

有价值的观察:需要演出的位置,价值是最长上升链(或者叫前缀 \(\max\)

并且注意到数据范围比较小,所以可以思考一些神秘做法,比如魔改 dijkstra

考虑访问到一个结点,需要维护的信息。容易发现需要维护 id,mx,cnt,c,分别表示编号、曾经的最大值、演出次数、以及当前的积蓄

如果我们入不敷出,则直接在增加若干次在最大值处的演出即可

所以直接朴素维护(松弛操作被魔改成了一个贪心或者 DP 的形式)

优先队列的排序方式也转化为演出次数较少者(第一关键字)、剩余积蓄较多者(第二关键字)

无解是好判断的,不连通即无解

代码

多测记得清空!!!(唯一一道一次 AC 的题目,悲)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=805,M=3e3+5,INF=4e18;
int n,m,p,a[N],head[N],tot,ans;bool vis[N][N];
struct Edge{int to,nxt,val;}e[M];
struct node{
    int id,mx,cnt,c;
    bool operator < (const node &rhs)const{
        if(cnt!=rhs.cnt)return cnt>rhs.cnt;
        return c<rhs.c;
    }
};
priority_queue<node> q;
inline void add(int u,int v,int w){
    e[++tot]={v,head[u],w};head[u]=tot;
    return;
}
inline void dijkstra(){
    q.push({1ll,1ll,0ll,p});
    while(!q.empty()){
        int u=q.top().id,mxi=q.top().mx,cnt=q.top().cnt,c=q.top().c;q.pop();
        if(vis[u][mxi])continue;
        vis[u][mxi]=true;
        if(u==n)ans=min(ans,cnt);
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to,w=e[i].val,mxj=(a[v]>a[mxi]?v:mxi);
            int t=(max(w-c,0ll)+a[mxi]-1)/a[mxi];
            q.push({v,mxj,cnt+t,c+t*a[mxi]-w});
        }
    }
    return;
}
inline void init(){
    memset(vis,false,sizeof(vis));
    memset(head,0,sizeof(head));tot=0;
    return;
}
inline void solve(){
    cin>>n>>m>>p;ans=INF;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1,u,v,w;i<=m;i++){cin>>u>>v>>w;add(u,v,w);}
    dijkstra();
    cout<<(ans>=INF?-1:ans)<<'\n';
    return;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T;cin>>T;while(T--)init(),solve();
    return 0;
}

T6

其实有点结论题那个感觉,想到了就想到了,想不到就只能暴力咯

题意

给定 \(n\)\(m\) 边无向图,初始存在 \(k\) 个感染源,接下来有 \(d\) 天传播时间。在第 \(i\) 天内,距离已被感染的结点 \(\le x_i\) 的结点会被感染。求所有结点的感染时间

题解

\(O(nd)\) 的暴力是好想的,然而需要优化

观测到 \(m\) 并没有特别大,所以考虑复杂度改至与 \(m\) 有关

然后就做完了(有点生成树那个感觉?)

其实就是每次维护边而不再维护点,容易发现一条边最多被访问一次,所以总复杂度就是 \(O(m)\) 的,当然还有优先队列的一支 \(\log\)

没有看懂和最短路的关联

代码

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int n,m,k,d,x[N],head[N],tot,tim[N];bool vis[N];
struct Edge{int to,nxt,val;}e[N<<1];
struct edge{
    int v,w;
    bool operator < (const edge &rhs)const{
        return w>rhs.w;
    }
};
priority_queue<edge> E;
queue<edge> q;
inline void add(int u,int v,int w){
    e[++tot]={v,head[u],w};head[u]=tot;
    return;
}
inline void work(int u,int lim,int t){
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to,w=e[i].val;
        if(vis[v])continue;
        if(lim<w)q.push({v,w});
        else E.push({v,x[t]-lim+w});
    }
    return;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int u,v,w;cin>>u>>v>>w;
        add(u,v,w),add(v,u,w);
    }
    cin>>k;
    for(int c=1;c<=k;c++){
        int x;cin>>x;
        vis[x]=true;tim[x]=0;
        for(int i=head[x];i;i=e[i].nxt){
            int v=e[i].to,w=e[i].val;
            E.push({v,w});
        }
    }
    cin>>d;
    for(int t=1;t<=d;t++)cin>>x[t];
    for(int t=1;t<=d;t++){
        while(!E.empty()&&E.top().w<=x[t]){
            int v=E.top().v,w=E.top().w;E.pop();
            if(!vis[v]){
                vis[v]=true;tim[v]=t;
                work(v,x[t]-w,t);
            }
        }
        while(!q.empty()){E.push(q.front());q.pop();}
    }
    for(int i=1;i<=n;i++)cout<<(vis[i]?tim[i]:-1)<<'\n';
    return 0;
}

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-07-04 20:27  sunxuhetai  阅读(22)  评论(1)    收藏  举报