海亮寄 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;
}
后记
世界孤立我任它奚落
完结撒花!

浙公网安备 33010602011771号