SDU程序设计思维与实践作业Week7

A TT的魔法猫

题目

题目
Input&&Output
inputoutput
Sample

#input:
3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4
#output:
0
0
4

题解

1.首先胜负具有传递性,胜负关系有很多因此我们可以把他抽象为一个图胜负关系也就
是边是否可达
2.因此我们发现这是一个求图中任意两点是否可达的问题
3.因此采用floyd算法,使用一种类似于动态规划的方法去求
a可达b可以拆为: a 是否可通过 k 到达 b(k为图中任意一点)

C++代码

/*
胜负判定——求闭包 
*/
#include<iostream>

using namespace std;
const int maxn = 600;
int n,m,A,B,count=0,dis[maxn][maxn];

void init(){
	count=0;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			dis[i][j] = 0;
}
void floyd(){
	for(int k=0;k<n;k++)
		for(int i=0;i<n;i++){
			if(dis[i][k]<=0) continue;
			for(int j=0;j<n;j++){
				if(dis[i][k]!=0 && dis[k][j]!=0 && (dis[i][k] + dis[k][j] < dis[i][j] || dis[i][j] == 0))
					dis[i][j] = dis[i][k] + dis[k][j];
			}
		}	
}
void out(){
	for(int i=0;i<n;i++)
		for(int j=i+1;j<n;j++)
			if(dis[i][j]==0&&dis[j][i]==0)
				count++;
				
	cout<<count<<endl;
}
int main(){
	int group_n,group=0;
	cin>>group_n;
	for(group=0;group<group_n;group++){
		cin>>n>>m;
		init();
		for(int i=0;i<m;i++){
			cin>>A>>B;
			dis[A-1][B-1] = 1;
		}
		floyd();
		out();
	}		
	return 0;
}

B TT的旅行日记

题目

题目
Input&&Output
inputoutput
Sample

#input:
4 1 4
4
1 2 2
1 3 3
2 4 4
3 4 5
1
2 4 3
#output:
1 2 4
2
5

题解

1.显然这是一道求单源最短路的问题,然鹅这道题增加了商业线,比较简单可以想到
的是对于每一条商业线加进去依次遍历寻找最优解,那么如何实现
2.因此我们通过另一种方法,最优路无非有两种含商业线与不含商业线
3.	对于不含的 我们只要依次从起始位置到终止位置的djicstra就可以得到
	对于含商业线的,我们把他拆为三部分 起始到商业线一端+商业线+终止到商业线一端,两次dijcstra就可以了
4.接下来就是如何求解线路的问题了,首先由于要输出使用了哪一条商业线因此当我们
需要更新更短线路时应该记录商业线,其次由于需要路径因此我们采用记录每个节点前
驱的方式记录线路,最后只要通过类似递归的方式就可得出正确线路

C++代码

#include<stdio.h>
#include<queue>
#include <string.h>
using namespace std;

const int maxn = 1e5+100;

int pre1[maxn],dis1[maxn],pre2[maxn],dis2[maxn],n, inf=1e6,s,e,m,k,tot=0,head[maxn],mid[maxn];
int ansu=-1,ansv=-1,ans=inf;
priority_queue<pair<int,int>> q;
bool flag=true;

struct edge{//前向星 w权值 next this到to有边 next前向星的下一个 
	int next,to,w;
}edge[maxn];

void add(int u,int v,int w){//加边
	tot++;
	edge[tot].to=v;
	edge[tot].w=w;
	edge[tot].next=head[u];
	head[u]=tot;
}

bool vis[maxn];

void init(){
	memset(head,0,maxn);
	tot=0;
	ansu=-1,ansv=-1,ans=inf;
	for(int i=0;i<n;i++){
		vis[i]=false;
		dis1[i] = inf;
		pre1[i] = i;
		dis2[i] = inf;
		pre2[i] = i;
	}
}

void dij(int begin,int *dis,int *pre){
	int curr;
	while(!q.empty()) q.pop();
	for(int i=0;i<n;i++) vis[i] = false;
	dis[begin]=0;
	q.push(make_pair(0,begin));
	while(!q.empty()){
		curr = q.top().second;q.pop();
		if(vis[curr]) continue;
		vis[curr]=true;
		for(int i=head[curr];i;i=edge[i].next){//松弛
			int v=edge[i].to;//终点
			int w=edge[i].w;//钱
			if(dis[curr] + w < dis[v]){
				dis[v] = dis[curr] + w;
				pre[v] = curr;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
}

void out(int b,int ansu,int ansv,int end){
	int end1 = ansu,end2=ansv,p=0;
	while(s!=end1){
		mid[p] = end1;
		p++;
		end1=pre1[end1];
	}
	printf("%d",b+1);
	for(int i=p-1;i>=0;i--)
		printf(" %d",mid[i]+1);
	while(e!=end2){
		printf(" %d",end2+1);
		end2=pre2[end2];
	}
	if(ansu!=ansv) printf(" %d",end+1);
}

int main(){
	int u,v,w;
	bool fla=true;
	while(scanf("%d %d %d",&n,&s,&e) != EOF){
		s=s-1;e=e-1;
		scanf("%d",&m);
		init();
		for(int i=0;i<m;i++){
			scanf("%d %d %d",&u,&v,&w);
			add(u-1,v-1,w);
			add(v-1,u-1,w);
		}
		dij(s,dis1,pre1);
		dij(e,dis2,pre2);
		ans=dis1[e];
		scanf("%d",&k);
		for(int i=0;i<k;i++){
			scanf("%d %d %d",&u,&v,&w);
			u=u-1;v=v-1;
			if(min(dis1[u]+dis2[v],dis1[v]+dis2[u]) + w < ans){ 
				ans = min(dis1[u]+dis2[v],dis1[v]+dis2[u]) + w;
				ansu = u;ansv = v;
			}
		}
		flag=true;
		if(ansu==-1&&ansv==-1){
			flag=false;
			ansu=e;ansv=e;
		}else{
			if(dis1[ansu]+dis2[ansv] > dis1[ansv]+dis2[ansu]) swap(ansu,ansv);
		}
		//s->u + u->v + v->e   ansu是换乘点
		if(!fla) printf("\n");
		fla=false;
		out(s,ansu,ansv,e);	
		if(flag) printf("\n%d\n",ansu+1);
		else printf("\nTicket Not Used\n");
		printf("%d\n",ans);
	}
	return 0;
}

C TT的美梦

题目

题目
Input&&Output
inputoutput

Sample

#input:
2
5
6 7 8 9 10
6
1 2
2 3
3 4
1 5
5 4
4 5
2
4
5
10
1 2 4 4 5 6 7 8 9 10
10
1 2
2 3
3 1
1 4
4 5
5 6
6 7
7 8
8 9
9 10
2
3 10
#output:
Case 1:
3
4
Case 2:
?
?

题解

1.题目表达也比较明显是一道求单源最短路的问题(城市就是点,税就是边)
2.然鹅本题我们发现这道题有负边因此简单的dijcstra不可以解决,我们需要加入对负
环的处理
3.此时我们对于出点的判断方式需要改变,因为可能存在负环因此一个点第一次被弹出
后不一定就不会再走,因此我们采用另一种思路,到达这个点经过的点数一定小于等于
它的序号,如果超过说明存在负环那么这个点可到达的任意点均处于负环中,此时这个
点也就不可能会继续松弛了。这也就是我们常用的spfa算法求解负环
4.细节处理:
点可达的任一点可通过dfs寻找
由于弹出后还可能再进入因此vis矩阵应该在加入后再次指令以便下次进入
由于负环中的点不再松弛因此需引入flag标记矩阵标记不需要松弛的点

C++代码

#include <iostream>
#include <math.h>
#include <queue>
#include <string.h>
using namespace std;

const int N=300,inf=1e9,maxn=1e6+500;

struct edge{
	int next,to,w;//前向星 w权值 next this到next有边 to前向星的下一个 
}e[maxn];

int head[N],n,m,tot=-1,a[N],dis[N],cou[N],vis[N];
bool flag[N];
queue<int>q;//记录待松弛点 

void add(int u,int v,int w){//加边 
	e[++tot].to=v;
	e[tot].w=w;
	e[tot].next=head[u];
	head[u]=tot;
}

void dfs(int s){
	flag[s]=true;
	for(int i=head[s];i;i=e[i].next){
		int u=e[i].to;//u在负环中 
		if(!flag[u]) dfs(u);
	}
} 

void spfa(int s){
	while(!q.empty()) q.pop();//清空队列  
	for(int i=0;i<=n;i++){//初始化 vis矩阵(是否访问过)计数矩阵 dis距离矩阵 flag标记矩阵 
		vis[i]=0; cou[i]=0; dis[i]=inf; flag[i]=false;
	}
	dis[s]=0;
	vis[s]=1;
	q.push(s);//加入第一个 
	while(!q.empty()){
		int u=q.front();q.pop();
		vis[u]=0;//取出点后因为会二次访问因此将访问矩阵置零 
		if(flag[u]) continue;
		for(int i=head[u];i;i=e[i].next){//遍历 点可达边 
			int v=e[i].to;//终点 
			int w=e[i].w;//税 
			if(flag[v]) continue;
			if(dis[v]>(dis[u]+w)){//税更少更新路径 
				cou[v]=cou[u]+1;//到达此点需要经过的点的个数 
				if(cou[v]>=n) {//超过n证明存在负环
					dfs(v);//负环中所有点均不需要访问 
					continue;
				}
				dis[v]=dis[u]+w;//更新dis 
				if(!vis[v]) {//v未被松弛
					q.push(v);
					vis[v]=1;
				}	
			}
		} 
	}
}
int main()
{
	int T;
	cin>>T;
	for(int j=1;j<=T;j++){
		memset(head,0,sizeof(head)); 
		tot=0;
		cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i];//读权重
		cin>>m;
		for(int i=1;i<=m;i++){
			int u,v,w;
			cin>>u>>v;
			w=pow(a[v]-a[u],3);
			add(u,v,w);//加边 
		}
		spfa(1);//远点是1
		cout<<"Case "<<j<<":"<<endl;
		int q;
		cin>>q;
		for(int i=1;i<=q;i++){
			int P;
			cin>>P;
			if(flag[P]||dis[P]<3||dis[P]==inf) cout<<"?"<<endl;
			else cout<<dis[P]<<endl;	
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值