A-氪金帶東
題目
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:
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:
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:
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;
}