SDU程序設計思維與實踐作業Week6

A-氪金帶東

題目

題目
Input&&Output:
INPUT&&OUTPUT
Sample:

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

題解

1.本題要求任意點可以到達的最長距離
2.分析可得首先本題的圖是一個連通圖,而且根據邊數可得沒有環
3.再者要求點出發到達的最遠距離,我們不妨這樣想,點出發到達的最遠距離實際上
也是最遠距離出發到他的距離
4.顯然從直徑的某一個端點出發(並進行dfs)到該點可以得到它的最遠距離
5.直徑兩個端點,因此我們只要選出較大的就好了
6.同樣我們從任意一個點出發dfs得到的最遠點就是直徑的某一個端點,而從直徑dfs
的最遠位置就是直徑的另一個端點(因此本題3次dfs)

C++代碼

//代碼提供兩種 第一種存邊使用鄰接表 dfs使用循環 另一種使用鏈式前向星 dfs
//採用遞歸 ;鄰接矩陣過於佔用內存,對於本題會爆內存(可能我寫的不夠好233

//1.鄰接表
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int maxn=1e4+500;
int mark[maxn],MAX[maxn],MAX2[maxn],N;
vector<int> w[maxn],e[maxn];
queue<int> q;
void init(int m){
	for(int i=0;i<m;i++){
		mark[i] = 0;
		MAX[i] = 0;
		MAX2[i] = 0;
		w[i] = vector<int>(0);
		e[i] = vector<int>(0);
	}
}
int dfs(int x){
	q.push(x);
	int m=0,mid=0;
	MAX[x] = 0;
	while(!q.empty()){
		x = q.front();q.pop();
		if(mark[x]==0){
			mark[x] = 1;
			for(int i=0;i<e[x].size();i++){
				if(mark[e[x][i]]==0){
					q.push(e[x][i]);
					if(MAX[x]+w[x][i]>MAX[e[x][i]]) MAX[e[x][i]] = MAX[x]+w[x][i];
				}
			}
		}
	}
	for(int i = 0;i<N;i++){
		if(MAX[i]>mid){
			m=i;
			mid=MAX[i];
		}
		mark[i] = 0;
	}
	return m;
}
int main(){
	int a,b,m;
	while(cin>>N){
		init(N);
		for(int i=0;i<N-1;i++){
			cin>>a>>b;
			e[a-1].push_back(i+1);
			w[a-1].push_back(b);
			e[i+1].push_back(a-1);
			w[i+1].push_back(b);
		}
		m = dfs(0);
		for(int i=0;i<N;i++) {MAX[i]=0;}
		m = dfs(m);
		for(int i=0;i<N;i++){MAX2[i] = MAX[i];MAX[i] = 0;}
		dfs(m);
		for(int i=0;i<N;i++) cout<<max(MAX[i],MAX2[i])<<endl;
	}
	return 0;
}
//2.鏈式前向星
#include<iostream>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn=1e4+500;
int mark[maxn],MAX[maxn],MAX2[maxn],N,tot=0,head[maxn],vm;

struct Edge
{
	int u,v,w,next;
}Edges[2*maxn];

void addEdge(int u,int v,int w)
{
	Edges[tot].u=u;
	Edges[tot].v=v;
	Edges[tot].w=w;
	Edges[tot].next=head[u];
	head[u]=tot;
	tot++;
}

void init(int m){
	tot=0;vm=0;
	memset(head,-1,sizeof(head));
	for(int i=0;i<m;i++){
		mark[i] = 0;
		MAX[i] = 0;
		MAX2[i] = 0;
	}
}
void dfs(int x){
	mark[x] = 1;
	for(int i=head[x];i!=-1;i=Edges[i].next){
		if(mark[Edges[i].v]==0){
			MAX[Edges[i].v] = MAX[x]+Edges[i].w;	
			dfs(Edges[i].v);
		}
	}
}

int main(){
	int a,b,m,mid;
	while(cin>>N){
		init(N);
		for(int i=0;i<N-1;i++)
		{
			cin>>a>>b;
			addEdge(a-1,i+1,b);
			addEdge(i+1,a-1,b);
		}
		dfs(0);mid=0;//重置標記矩陣 max矩陣 vm:最遠端點 
		for(int i=0;i<N;i++) { if(mid<MAX[i]) {vm=i;mid=MAX[i];} MAX[i]=0;mark[i]=0;}
		dfs(vm);mid=0;
		vm=0;
		for(int i=0;i<N;i++){if(mid<MAX[i]) {vm=i;mid=MAX[i];} MAX2[i] = MAX[i];MAX[i] = 0;mark[i]=0;}
		dfs(vm);
		for(int i=0;i<N;i++) cout<<max(MAX[i],MAX2[i])<<endl;
	}
	return 0;
}

B-戴好口罩!

題目

題目
Input&&Output:
INPUT&&OUTPUT
Sample:

#input:
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
#output:
4
1
1

題解

1.本題實際上是求每個小團體的人數
2.每個團體只需要有一個代表人就好,因此我們可以想到利用並查集的思想,而且可
以使用路徑壓縮(僅僅關注領袖)
3.並查集的合併:實際上是把某一個集合的領袖變成另一個集合的領袖,(然而下一
次修改的時候我們就需要考慮更改節點領袖的個數問題,因此小樹掛大樹是我們需要
的。

C++代碼

#include<iostream>

using namespace std;
const int maxn = 1e5;
int st[maxn],rak[maxn];
int n,m,num;
void init(){
	for(int i = 0;i<n;i++){
		st[i] = i;
		rak[i]=1;
	}
}
int find(int i){
	if(st[i]!=i) return st[i]=find(st[i]);
	return i;	
}
bool unite(int x,int y){
	x = find(x),y = find(y);
	if(x==y) return false;
	if(x>y) swap(x,y);
	st[x] = y;
	rak[y] = rak[x]+rak[y];
	return true;
}
int main(){
	int c,cha=-1;
	while(cin>>n>>m){
		if(m == n&&m==0) break;
		init();
		while(m--){
			cha=-1;
			cin>>num;
			for(int i = 0;i<num;i++){
				cin>>c;
				if(cha!=-1) unite(c,cha);
				cha=c;
			}
		}
		cout<<rak[find(0)]<<endl;
	}
	return 0;
} 

C-掌握魔法の東東 I

題目

題目
Input&&Output:
INPUT&&OUTPUT
Sample:

#input:
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
#output:
9

題解

1.本題我們可以將油田看成一個個點,而傳送門實際上就是邊,黃河之水天上來實際
上也是給這些油田傳水
2.因此本題實際上是一個求最小遍歷圖的問題或者最小生成樹問題,我們將天也看做
一塊油田,他與每個油田都有連邊,然後我們去求最小生成樹就可以了
3.Kruskal算法:將點都放上,邊排序每次取最小可以增強圖的連通性的邊(也就是
說已連通的邊不需要再加邊(下述代碼所採用的)
4.prim算法:本質上與kruskal類似,只不過取邊然後擴充點集,直至覆蓋所有點
5.兩種算法各有優劣,與邊 點個數有關
6.連通性判斷:實際上也是一個路徑壓縮的並查集

C++代碼

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=1e5+500;
int m,n,root;
int u,v,w;
struct edge{
	int u,v,w;
	
	operator<(edge &x){
		return w<x.w;
	}	
};
int conn[maxn];
vector<edge> ve(0);
int find(int i) {return conn[i]==i ? i : conn[i]=find(conn[i]);}
edge c;
int main(){
	int MAX=0;
	cin>>n>>m>>root;
	for(int i=0;i<n+1;i++) conn[i]=i;
	for(int i = 0;i<m;i++){
		cin>>u>>v>>w;
		c.u=u-1,c.v=v-1,c.w=w;
		ve.push_back(c);
	}
	sort(ve.begin(),ve.end());
	for(int i=0;i<ve.size();i++){
		c=ve[i];
		find(c.u),find(c.v);
		if(conn[c.u]!=conn[c.v]){
			conn[conn[c.u]]=conn[c.v];
			if(c.w>MAX) MAX=c.w;
		}
	}
	cout<<MAX<<endl;
	return 0;
}

D-數據中心

題目

題目
Input&&Output:
INPUT&&OUTPUT
Sample:

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

題解

1.本題求流水線最小耗時,我們把它看成層實際上每層流水線同時開始,它到下一層
的最優時間取決於本層最大的時間,而整個流水線運行時,它的最優時間實際上是所
有層的最小時間(所有層的最大時間的最大值
2.我們這樣去想,所有層最大時間的最大值不就是所有邊的最大值麼,那麼我們實際
上是求一組變,它既能使流水線連通(圖連通)而且邊要儘可能的小,因此是一個最
小生成樹問題

C++代碼

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=1e5+500;

struct edge{
	int u,v,w;
	
	operator<(edge &x){
		return w<x.w;
	}	
};
int conn[maxn];
vector<edge> ve(0);
int find(int i) {return conn[i]==i ? i : conn[i]=find(conn[i]);}
edge c;
int main(){
	int n,w,u,v,len=0;
	cin>>n;
	for(int i=0;i<n+1;i++) conn[i]=i;
	for(int i = 0;i<n;i++){
		cin>>w;
		c.u=n,c.v=i,c.w=w;
		ve.push_back(c);
	}
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			cin>>w;
			c.u=i,c.v=j,c.w=w;
			if(i<j) ve.push_back(c);
		}
	sort(ve.begin(),ve.end());
	for(int i=0;i<ve.size();i++){
		c=ve[i];
		find(c.u),find(c.v);
		if(conn[c.u]!=conn[c.v]){
			conn[conn[c.u]]=conn[c.v];
			len+=c.w;
		}
	}
	cout<<len<<endl;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章