赛前训练1 最短路

T1

分层图一般套路:dis 数组多设置一维表示层数、松弛时讨论是否换层即可。

参考实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e4+5,K=15;
int n,m,k,s,t;
struct EDGE{
	int v,w;
};
struct NODE{
	int v,w,l;
	bool operator < (const NODE &b) const{
		return w>b.w;
	}
};
int dis[N][K];
bool vis[N][K];
vector<EDGE> G[N];

void dijkstra(){
	memset(dis,0x3f,sizeof dis);
	dis[s][0]=0;
	priority_queue<NODE> pq;
	pq.push({s,0,0});
	while(!pq.empty()){
		auto cur=pq.top();
		pq.pop();
		if(vis[cur.v][cur.l])
			continue;
		vis[cur.v][cur.l]=1;
		for(auto i:G[cur.v]){
			if(dis[i.v][cur.l]>dis[cur.v][cur.l]+i.w){
				dis[i.v][cur.l]=dis[cur.v][cur.l]+i.w;
				pq.push({i.v,dis[i.v][cur.l],cur.l});
			}
			if(cur.l<k&&dis[i.v][cur.l+1]>dis[cur.v][cur.l]){
				dis[i.v][cur.l+1]=dis[cur.v][cur.l];
				pq.push({i.v,dis[i.v][cur.l+1],cur.l+1});
			}
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m>>k>>s>>t;
	s++,t++;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w,u++,v++;
		G[u].push_back({v,w});
		G[v].push_back({u,w});
	}
	dijkstra();
	int ans=1e9;
	for(int i=0;i<=k;i++)
		ans=min(ans,dis[t][i]);
	cout<<ans;
	return 0;
} 

T2

奇偶最短路:用于判断 \(a \to b\) 是否能走 \(x\) 步到达。

具体而言,维护 dis1 表示奇数最短路,dis2 表示偶数最短路。

dijkstra 中转移时,根据奇偶变化,dis2 松弛 dis1,dis1 松弛 dis2 即可。

判断时,若 \(x\) 为奇数且大于等于 \(dis1_{a,b}\),或 \(x\) 为偶数且大于等于 \(dis2_{a,b}\),都是能走到的,否则不能走到。

对于这个题,需要判断孤点的情况,因为孤点也跑 bfs 的话会导致其 \(dis2=0\),从而误认为其他点能从它开始走到。

参考实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=5e3+5,K=1e6+5;
int n,m,k;
struct NODE{
	int t,d,id;
};
vector<NODE> q[K];
vector<int> G[N];
int dis[N][2];
bool vis[N],ans[K];

void bfs(int s){
	memset(dis,0x3f,sizeof dis);
	queue<int> q;
	q.push(s);
	vis[s]=1;
	dis[s][0]=0;
	while(!q.empty()){
		int cur=q.front();
		vis[cur]=0;
		q.pop();
		for(int i:G[cur]){
			if(dis[i][0]>dis[cur][1]+1){
				dis[i][0]=dis[cur][1]+1;
				if(!vis[i])
					vis[i]=1,q.push(i);
			}
			if(dis[i][1]>dis[cur][0]+1){
				dis[i][1]=dis[cur][0]+1;
				if(!vis[i])
					vis[i]=1,q.push(i);
			}
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m>>k;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	for(int i=1,s,t,d;i<=k;i++){
		cin>>s>>t>>d;
		q[s].push_back({t,d,i});
	}
	for(int i=1;i<=n;i++){
		if(q[i].size()&&G[i].size()){
			bfs(i);
			for(auto j:q[i])
				if(j.d%2==0&&j.d>=dis[j.t][0]||j.d%2==1&&j.d>=dis[j.t][1])
					ans[j.id]=1;
		}
	}
	for(int i=1;i<=k;i++)
		cout<<(ans[i]?"TAK\n":"NIE\n");
	return 0;
}

T3

最短路分割 + 贪心

对于 \(u,v\),有 \(dis_{1,n}=dis_{1,u}+dis_{u,v}+dis_{v,n}\),现在 \(dis_{u,v}\) 将被替换为 \(1\),我们希望变化尽可能小,显然 \(dis_{u,v}\) 应当尽可能小。

从贪心的角度考虑,\(dis_{u,v}\) 尽可能小,等价于 \(u,v\)\(1\) 的距离之差尽可能小,于是我们可以讲 \(S\) 按照与 \(1\) 的距离排序,每次考虑选择相邻节点连边即可。

值得注意的是,我连了边,最短路可能并不经过它,所以我们最后还得和原图最短路取 \(\min\)

参考实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=2e5+5;
int n,m,k;
int a[N],dis1[N],dis2[N];
bool vis[N];
vector<int> G[N];

bool cmp(int x,int y){
	return dis1[x]<dis1[y];
}
void bfs(int s,int *dis){
	for(int i=1;i<=n;i++)
		dis[i]=1e18;
	memset(vis,0,sizeof vis);
	queue<int> q;
	q.push(s);
	dis[s]=0;
	while(!q.empty()){
		int cur=q.front();
		q.pop();
		if(vis[cur])
			continue;
		vis[cur]=1;
		for(int i:G[cur]){
			if(dis[i]>dis[cur]+1){
				dis[i]=dis[cur]+1;
				q.push(i);
			}
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m>>k;
	for(int i=1;i<=k;i++)
		cin>>a[i];
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	bfs(1,dis1),bfs(n,dis2);
	sort(a+1,a+k+1,cmp);
	int ans=-1e18;
	for(int i=1;i<k;i++)
		ans=max(ans,dis1[a[i]]+dis2[a[i+1]]+1);
	cout<<min(ans,dis1[n]);
	return 0;
}

T4

差分约束。

简单来说,对于诸如下面这样的不等式组:

\[\begin{cases} y_1-x_1 \ge w_1 \\ y_2-x_2 \ge w_2 \\ \cdot \cdot \cdot \\ y_m-x_m \ge w_m \end{cases} \]

\(x_i\)\(y_i\) 建一条权值为 \(w_i\) 的有向边(\(i \in [1,m]\)),并虚拟 \(0\) 号源点并向所有 \(x_i\) 连一条权值为 \(0\) 的有向边,即可得到一张有向图。

可以通过在这张有向图判断能不能跑出最短路(即判负环)的方式证明不等式组有无解,画图易证。

把不等式组的所有符号换成 \(\le\),则通过判断能不能跑出最长路(即判正环)亦可解决。

同时,最长路通常解决最小化问题,最短路通常解决最大化问题。

回归本题,看到「至少」等字眼,我们需要发现差分约束模型。

运用前缀和的思想,我们令 \(sum_i\) 表示前 \(i\) 小时的收银员数量。

显然,\(sum_i-sum_{i-1} \ge 0\)

同时,\(sum_i-sum_{i-1} \le t_i\),其中 \(t_i\) 表示第 \(i\) 小时申请的人数。

考虑到还有个下界 \(R_i\),因此有 \(sum_i-sum_{i-8} \ge R_i\)

依照上述不等式建图,然后枚举最少人数并跑正环判断即可。需要特判 \(i-8<0\) 的情况。

参考实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=48;
int _,n;
int r[N],t[N],dis[N],cnt[N];
bool vis[N];
struct EDGE{
	int v,w;
};
vector<EDGE> G[N];

bool spfa(int s){
	memset(dis,-0x3f,sizeof dis);
	dis[s]=0;
	memset(vis,0,sizeof vis);
	memset(cnt,0,sizeof cnt);
	vis[s]=1;
	queue<int> q;
	q.push(s);
	while(!q.empty()){
		int cur=q.front();
		q.pop();
		vis[cur]=0;
		for(auto i:G[cur]){
			if(dis[i.v]<dis[cur]+i.w){
				dis[i.v]=dis[cur]+i.w;
				cnt[i.v]=cnt[cur]+1;
				if(cnt[i.v]>24)
					return 0;
				if(!vis[i.v])
					vis[i.v]=1,q.push(i.v);
			}
		}
	}
	return 1;
}
bool check(int x){
	for(int i=0;i<=24;i++)
		G[i].clear();
	G[0].push_back({24,x});
	for(int i=1;i<=24;i++){
		G[0].push_back({i,0});
		G[i-1].push_back({i,0});
		G[i].push_back({i-1,-t[i]});
		if(i<=8)
			G[i+16].push_back({i,r[i]-x});
		else
			G[i-8].push_back({i,r[i]}); 
	}
	return spfa(0);
}
void solve(){
	memset(t,0,sizeof t);
	for(int i=1;i<=24;i++)
		cin>>r[i];
	cin>>n;
	for(int i=1,x;i<=n;i++)
		cin>>x,x++,t[x]++;
	for(int i=1;i<=n;i++){
		if(check(i)){
			cout<<i<<'\n';
			return;
		}
	}
	cout<<"No Solution\n";
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>_;
	while(_--)
		solve();
	return 0;
}
posted @ 2025-09-07 20:47  _KidA  阅读(3)  评论(0)    收藏  举报