9.2 图论专项四模拟赛小记

A.最短路

原题洛谷指路:P1144 最短路计数

看评测一开始 0pts,死于无向边看成单向边。后来在洛谷提交的时候调了好久好久,死于数组开小。

其他没什么,就是一个 dijkstra 的板子。转移路径的时候,若这条路比当前的路长度小,那这条路的答案就更新为转移过来新路的答案;若这条路和当前路长度一样,那将答案加上新路。

Code P1144
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
const int mod = 1e5 + 3;
int n, m;
int vis[N];
priority_queue< pair<int, int> > q;
int idx, e[N], w[N], nxt[N], head[N];
struct node{int d, tot;}ans[N];
void add(int x, int y, int z)
{
	e[++ idx] = y;
	w[idx] = z;
	nxt[idx] = head[x];
	head[x] = idx;
}
void dij()
{
	ans[1].d = 0;
	ans[1].tot = 1;
	q.push(make_pair(0, 1));
	while(q.size())
	{
		int x = q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x] = 1;
		for(int i = head[x]; i; i = nxt[i])
		{
			if(ans[e[i]].d == ans[x].d + 1) ans[e[i]].tot = (ans[e[i]].tot + ans[x].tot) % mod;
			else if(ans[e[i]].d > ans[x].d + 1)
			{
				ans[e[i]].d = ans[x].d + 1;
				ans[e[i]].tot = ans[x].tot % mod;
				q.push(make_pair(-ans[e[i]].d, e[i]));
			}
		}
	}
}
int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++ ) ans[i].d = 1e9;
	while(m -- )
	{
		int x, y;
		scanf("%d%d", &x, &y);
		add(y, x, 1);
		add(x, y, 1);
	}
	dij();
	for(int i = 1; i <= n; i ++ ) printf("%d\n", ans[i].tot % mod);
	return 0;
}

B.灾后重建

洛谷原题指路:P1948 [USACO08JAN] Telephone Lines S

总费用来自最长的路径。即求最大值最小。

一看到最大值最小、最小值最大第一反应就是二分答案。

于是本题我写的是 dijkstra + 二分答案。

难点还是在于 check。设 x 为二分的费用,则要求跑最短路时边权大于 x 的边数 <= k。即对于每一条边,只要边权 > x,就需要使用一个免费电话杆。

这里很巧妙的重新设置边权,需使用免费电话杆的边权为 1,否则为 0。跑一遍 dijkstra,实际上就是统计边权 > x 的边数。

Code P1948
#include<bits/stdc++.h>
using namespace std;

const int N=2e4+10;
const int inf=0x7fffffff;

int n,m,k;
int l,r,ans=inf;
int vis[N],d[N],t[N];
int idx,e[N],w[N],nxt[N],head[N];

void add(int x,int y,int z){
	e[++idx]=y;
	w[idx]=z;
	nxt[idx]=head[x];
	head[x]=idx;
}

void dij(){
	priority_queue<pair<int, int> > q;
	q.push(make_pair(0,1));
	for(int i=1;i<=n;i++){
		vis[i]=0;
		d[i]=inf;
	}
	d[1]=0;
	while(q.size())
	{
		int x=q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=head[x];i;i=nxt[i]){
			if(d[e[i]]>d[x]+t[i]){
				d[e[i]]=d[x]+t[i];
				q.push(make_pair(-d[e[i]],e[i]));
			}
		}
	}
}

bool check(int x){
	for(int i=1;i<=idx;i++)
	{
		if(w[i]>x) t[i]=1;
		else t[i]=0;
	}
	dij();
	return (d[n]<=k) ? 1 : 0;
}

int main(){
	scanf("%d%d%d", &n,&m,&k);
	while(m -- ){
		int x, y, z;
		scanf("%d%d%d", &x,&y,&z);
		add(x,y,z);
		add(y,x,z);
		r=max(r,z);
	}
	
	while(l<=r)
	{
		int mid=(l+r)>>1; 
		if(check(mid))
		{
			r=mid-1;
			ans=min(ans,mid);			
		}
		else l=mid+1;
	}
	if(ans==inf) puts("-1");
	else printf("%d",ans);
	return 0;
}

但好像并不需要最短路,可以直接用 bfs + 二分,由于 bfs 是 O(n) 的所以会跑的飞快。所以如果数据范围开大 10 倍,spfa、dij 什么的都会寄掉。

本题还是 bfs 比较强。至于我为什么跟本没想到,主要是平时几乎没写过 bfs,所以在这里也练练。思路是一样的。

Code bfs
#include<bits/stdc++.h>
using namespace std;

const int N=2e4+10;
const int inf=0x3f3f3f3f;

int n,m,k;
int l,r,ans=inf;
int idx,e[N],w[N],nxt[N],head[N];
int vis[N],dis[N];

void add(int x,int y,int z){
	e[++idx]=y;
	w[idx]=z;
	nxt[idx]=head[x];
	head[x]=idx;
}

bool check_bfs(int x){
	memset(dis,inf,sizeof dis);
	dis[1]=0,vis[1]=1;
	queue<int> q;
	q.push(1);
	while(q.size()){
		int t=q.front();
		q.pop();
		vis[t]=0;//回溯 
		for(int i=head[t];i;i=nxt[i]){
			int p=(w[i]>x)?1:0;
			if(dis[e[i]]>dis[t]+p){
				dis[e[i]]=dis[t]+p;
				if(! vis[e[i]]){
					vis[e[i]]=1;
					q.push(e[i]);
				}
			}
		}
	}
	if(dis[n] <= k) return 1;
	return 0;
}

int main(){
	
	scanf("%d%d%d",&n,&m,&k);
	while(m--){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
		r=max(r,z);
	}
	
	while(l<=r){
		int mid=(l+r)>>1;
		if(check_bfs(mid)){
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}

	if(ans==inf) puts("-1");
	else printf("%d",ans);
	return 0;
}

C.卡尔距离

原题:bzoj 4152.The Captain

题意:给平面上 n 个点,定义 (x1,x2) 到 (y1,y1) 的费用为 min(|x1-x2|,|y1-ty2|)。求从 1 号点到 n 号点的最小费用。

本题思路上稍微一点的难度就是建图。图建好了直接跑最短路就好。

画图会发现,当有三个点 i, j, k,且 i 与 j 相邻, j 与 k 相邻时,我们不会把 i 与 k 连起来,而是把 i 与 j、 j 与 k 连起来。这样才会缩短 i 与 k 之间的距离。

于是根据横坐标和纵坐标分别两次排序,每次将相邻的两个点之间连边后跑最短路即可。

Code bzoj 4152
#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=1e6+10;
const ll inf=0x3f3f3f3f;

int n;
int vis[N];
ll d[N],w[N];
int idx,e[N],nxt[N],head[N];
priority_queue<pair<ll,int> > q;
struct node{ll x,y; int id;}a[N];

void add(int x,int y,ll z){
	e[++idx]=y;
	w[idx]=z;
	nxt[idx]=head[x];
	head[x]=idx;
}

bool cmp1(node b,node c){return b.x<c.x;}
bool cmp2(node b,node c){return b.y<c.y;}

void ad(){
}

void dij(){
	memset(vis,0,sizeof vis);
	memset(d,inf,sizeof d);
	d[1]=0;
	q.push(make_pair(0,1));
	while(q.size()){
		int x=q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=head[x];i;i=nxt[i]){
			if(d[e[i]]>d[x]+w[i]){
				d[e[i]]=d[x]+w[i];
				q.push(make_pair(-d[e[i]],e[i]));
			}
		}
	}
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",&a[i].x,&a[i].y);
		a[i].id=i;
	}
	sort(a+1,a+n+1,cmp1);
	for(int i=1;i<=n;i++){
		add(a[i].id,a[i+1].id,abs(a[i].x-a[i+1].x));
		add(a[i+1].id,a[i].id,abs(a[i].x-a[i+1].x));
	}
	sort(a+1,a+n+1,cmp2);
	for(int i=1;i<=n;i++){
		add(a[i].id,a[i+1].id,abs(a[i].y-a[i+1].y));
		add(a[i+1].id,a[i].id,abs(a[i].y-a[i+1].y));
	}
	dij();
	printf("%lld",d[n]);
}

D.奶酪 - 学好语文很重要

原题:bzoj 2165.大楼

题意:给定一张有向图,求从 1 号点出发,到达编号 >= m 的点且经过边数最少的路径的边数。

读题的关键在于,对于通道 \((x,y,z)\) 需要注意到,你并不关心这具体是哪一层奶酪,你只需要知道从 x 走到 y 你得到的答案能增加 z。

题意挺绕的,如果没读懂题目的话建议多读几遍,好好理解一下。

注意到 n 很小,最终目标巨大。从题目中抽象出来问题后容易想到做法:倍增 + floyd。

\(f[k][i][j]\) 表示走 \(2^k\) 步以内,从 i 号点到 j 号点可得到的最大答案。矩乘优化,每次 check 一下。

Code bzoj 2165
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;

const int N=105;
const int inf=1e18;

ll m,ans;
int T,n;

struct M{
	ll a[N][N];
	M operator *(const M &b) const{
		M res;
		memset(res.a,-1,sizeof res.a);
		for(int k=1;k<=n;k++){
			for(int i=1;i<=n;i++){
				if(a[i][k]==-1) continue;
				for(int j=1;j<=n;j++){
					if(b.a[k][j]==-1) continue;
					res.a[i][j]=max(res.a[i][j],a[i][k]+b.a[k][j]);
				}
			}
		}
		return res;
	}
}f[65],x,y;

bool check(M res){
	for(int i=1;i<=n;i++) if(res.a[1][i]>=m) return 1;
	return 0;
}

void solve(){
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			ll t;
			scanf("%lld",&t);
			f[0].a[i][j]=(t)?t:-1;
		}
	}
	
	int t=0;
	while(t>=0){
		if(t>=64){puts("-1"); return;}
		f[t+1]=f[t]*f[t];
		if(check(f[t+1])) break;
		t++;
	}
	
	x=f[0],ans=1;
	for(int i=t;i>=0;i--){
		y=x*f[i];
		if(!check(y)){
			x=y;
			ans+=(1ll<<i);
		}
	}
	printf("%lld\n",ans+1);
}

int main(){
	scanf("%d",&T);
	while(T--) solve();
}

E.旅行到永久

原题:P4308 [CTSC2011] 幸福路径

n 很小所以想到用 floyd。枚举步数会炸,所以倍增 + dp。当 t 足够大时就可以近似的看成答案。

\(f[t][i][j]\) 表示从 i 到 j 走 \(2^k\) 步时能获得的最大答案。转移方程: \(f[t][i][j]=max(f[t−1][i][k]+f[t−1][k][j]*p^{2^{(t-1)}})\)

Code P4308
#include<bits/stdc++.h>
typedef double db;
using namespace std;

const int N=110;

int n,m,s;
db p,ans;
db a[N],f[35][N][N];

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
	scanf("%d%lf",&s,&p);
	for(int t=0;t<=33;t++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++) if(i!=j) f[t][i][j]=-1e9;
	while(m--){
		int x,y;
		scanf("%d%d",&x,&y);
		f[0][x][y]=a[y]*p;
	}
	
	for(int t=1;t<=32;t++){
		for(int k=1;k<=n;k++)
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++){
					if(f[t][i][j]<f[t-1][i][k]+f[t-1][k][j]*p) f[t][i][j]=f[t-1][i][k]+f[t-1][k][j]*p;
					if(i==s) ans=(ans,f[t][i][j]);
				}
		p*=p;
		if(p<=(1e-9)) break;
	}
	printf("%.1lf",ans+a[s]);
}

F.货物运输 - 航线与隧道

原题:P3008 [USACO11JAN] Roads and Planes G

如果不考虑被卡的话就是裸的 spfa(dij 不能跑负边),但众所周知,关于 spfa,他死了。所以本题我们可以用 deque 小小的优化一下。每次插入新元素时比较一下,如果比队头大就插后边。

虽然裸的 spfa 有 88pts。这么善良的出题人。

Code P3008
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+2;
const int inf=0x3f3f3f3f;

deque<int> q;
int n,m,k,s;
int vis[N], d[N];
int idx,e[N],w[N],head[N],nxt[N];

void add(int x,int y,int z){
	e[++idx]=y;
	w[idx]=z;
	nxt[idx]=head[x];
	head[x]=idx;
}

void spfa(){
	
	memset(d,inf,sizeof d);
	d[s]=0;
	vis[s]=1;
	q.push_back(s);
	
	while(q.size()){
		
		int x=q.front();
		q.pop_front();
		vis[x]=0;
		
		for(int i=head[x];i;i=nxt[i])
			if(d[e[i]]>d[x]+w[i]){
				d[e[i]]=d[x]+w[i];
				if(!vis[e[i]]){
					if(q.size()&&d[e[i]]>=d[q.front()]) q.push_back(e[i]);
                	else q.push_front(e[i]);
					vis[e[i]]=1;
				}
			}
	}
}

int main(){
	
	scanf("%d%d%d%d", &n,&m,&k,&s);
	while(m--){
		int x,y,z;
		scanf("%d%d%d", &x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	while(k--){
		int x,y,z;
		scanf("%d%d%d", &x,&y,&z);
		add(x,y,z);
	}
	
	spfa();
	
	for(int i=1;i<=n;i++){
		if(d[i]!=inf) printf("%d\n",d[i]);
		else puts("NO PATH");
	}
	
	return 0;
}

正解是根据题目性质缩点 + dij 吧。此坑待填。

posted @ 2023-09-04 16:02  Moyyer_suiy  阅读(26)  评论(1编辑  收藏  举报