算法練習12&貪心&並查集&Kruskal題&Dijkstra題

問題 A: 菱形圖案

題目描述
KiKi學習了循環,BoBo老師給他出了一系列打印圖案的練習,該任務是打印用“”組成的菱形圖案。
輸入
多組輸入,一個整數(2~20)。
輸出
針對每行輸入,輸出用“
”組成的菱形,每個“*”後面有一個空格。每輸出一個菱形的後面需要空一行。
在這裏插入圖片描述

#include<bits/stdc++.h>

#define ll long long
const int N=1005;
using namespace std;

int main(){
	int n;
	while(cin>>n){
		for(int i=0;i<=n;i++){
			for(int j=0;j<n-i;j++)
                cout<<' ';
			for(int k=0;k<=i;k++){
                cout<<'*';
                cout<<' ';
			}
           cout<<endl;
		}
		for(int i=0;i<=n-1;i++){
			for(int j=0;j<=i;j++)
                cout<<' ';
			for(int k=0;k<n-i;k++)
               {
                   cout<<'*';
                   cout<<' ';
               }
			cout<<endl;
		}
		cout<<endl;

	}
}

問題 E: 並查集

題目描述
在某個城市中住着n個人,現在給定關於這n個人的m條信息(即某2個人認識)。
假設所有認識的人一定屬於同一個單位,請計算該城市有多少個單位?
輸入
第1行的第1個值表示總人數n,第2個值表示總信息數m;第2行開始爲具體的認識關係信息
輸出
單位的個數
在這裏插入圖片描述

#include<bits/stdc++.h>

using namespace std;
const int N=1005;
int parents[N];//用來存放每棵樹的根節點
int ran[N];//秩,用來優化合並

int cont;
void init(int x){//初始化每個集合(樹),將每個元素作爲一個集合
    parents[x]=x;
    ran[x]=0;//
}
int findParet(int x){//路徑壓縮,遞歸的將每個節點的根節點指向整棵樹的根節點
    if(parents[x]!=x)
        parents[x]=findParet(parents[x]);
    return parents[x];
}
void merg(int x,int y){//用來合併
    x=findParet(x);//找到包含x的這課樹的根節點
    y=findParet(y);
    if(x==y)//如果相等代表在同一棵樹,在同一個集合,直接return
        return;
    cont--;//否則,就減一,集合(樹)個數少1
    if(ran[x]<ran[y]){//將秩大的作爲合併後的根節點
        parents[x]=y;
    }else{
        parents[y]=x;
        if(ran[x]==ran[y])//如果相等,需要將秩加一
            ran[x]++;
    }

}
int main()
{
    int n,m;
    while(~scanf("%d %d",&n,&m)){
            int x,y;
            cont=n;
            for(int j=1;j<=n;j++){
                init(j);
            }
            for(int i=0;i<m;i++){
                 cin>>x>>y;
                 merg(x,y);
            }
       cout<<cont<<endl;
    }



    return 0;
}

問題 B: 迷路的牛牛

題目描述
牛牛去犇犇老師家補課,出門的時候面向北方,但是現在他迷路了。雖然他手裏有一張地圖,但是他需要知道自己面向哪個方向,請你幫幫他。
輸入
每個輸入包含一個測試用例。
每個測試用例的第一行包含一個正整數,表示轉方向的次數N(N<=1000)。
接下來的一行包含一個長度爲N的字符串,由L和R組成,L表示向左轉,R表示向右轉。
輸出
輸出牛牛最後面向的方向,N表示北,S表示南,E表示東,W表示西。
在這裏插入圖片描述
想象成一個環,往左就減一,往右就加一

#include<bits/stdc++.h>

using namespace std;
const int N=1005;
char a[N];
char d[4]={'E','S','W','N'};//存放方向,順序要有規律,想象成一個環,往左就減一,往右就加一,這樣排列
int main()
{
int T;
int temp=3;//北方的位置
while(cin>>T){
    for(int i=0;i<T;i++){
        cin>>a[i];
    }
    for(int j=0;j<T;j++){
        if(a[j]=='L'){//如果向左就減一取模,保證不會越界
            temp=(temp-1)%4;
        }
        if(a[j]=='R'){//如果向右就減一取模,保證不會越界
            temp=(temp+1)%4;
        }
    }
    cout<<d[temp]<<endl;


}

    return 0;
}

問題 C: XP的午餐

題目描述
XP每天都會思考一個問題,今天午餐去哪裏吃?這是一個很重要的問題,這會影響到他下午的體力值。他的午餐預算是M元,現在有N種菜品,每一種菜品的價格和能夠提供的體力值已知(每種菜品只能選擇一次),請問如何選擇菜品能夠讓XP下午的體力值最大呢?
輸入
第一行:M元和菜品數量N。
接下來N行,每一行兩個整數,分別表示每一種菜品的價格(vi)和能夠獲得的體力值(wi)。
(0<N<=20,0<=M<=1000)(0<=vi<=50,0<=wi<=100)
輸出
最大體力值。
在這裏插入圖片描述
動態規劃思想

#include<bits/stdc++.h>

#define ll long long
const int N=1005;
using namespace std;

int a[N][N];//用來存放dptable
int w[N];//用來存放價格
int num[N];//存放體力值
int code[N];
int main()
{
    int T,n;
    while(cin>>n>>T){
           for(int i=1;i<=T;i++){
             scanf("%d",&w[i]);
             scanf("%d",&num[i]);

           }
           for(int k=1;k<=T;k++){
                for(int m=0;m<=n;m++){
                    if(k==1){//初始化dptable的初值
                        if(w[k]>m){
                            a[k][m]=0;
                        }else{
                            a[k][m]=num[k];
                        }
                        continue;
                    }
                    if(w[k]>m){//如果價格超過錢,不要
                            a[k][m]=a[k-1][m];
                        }else{//在要與不要取最大值
                            a[k][m]=max(a[k-1][m-w[k]]+num[k],a[k-1][m]);
                        }
                }
           }

           printf("%d\n",a[T][n]);

    }
   return 0;
}


問題 D: XP的寶藏

題目描述
幾經波折和磨難,XP終於拿到了寶庫的鑰匙和藏寶圖,他站在寶庫的左上角,這是寶庫的入口,出口在右下角。寶庫是由一個個小暗格組成的正方形,有些暗格裏面有寶藏,也有一些暗格沒有。暗格中佈滿了機關,所有開往左邊和上面的門都不能開啓,一旦開啓就會觸發機關,將被萬箭穿心。XP開始陷入深深的沉思,他希望活着走出寶庫;人總是有貪慾的,他還希望能夠帶走儘可能多的寶藏,你能否幫到他呢?
輸入
每組輸入第一個包括一個N,表示N*N的矩陣
第2~n+1行表示該藏寶圖 (0<=N<=20)
輸出
XP獲得的最多寶藏數
在這裏插入圖片描述
動態規劃思想

#include<bits/stdc++.h>

#define ll long long
const int N=1005;
using namespace std;

int G[N][N];//用來存放圖
int a[N][N];//dptable
int main()
{
    int T,n;
    while(cin>>T){
           for(int i=1;i<=T;i++){
                for(int j=1;j<=T;j++){
                    cin>>G[i][j];
                }
           }
           for(int k=0;k<=T;k++){
                for(int m=0;m<=T;m++){
                        if(k==0||m==0)//初始化table的初值
                        {
                            a[k][m]=0;
                            continue;
                        }
                        if(k==1&&m==1){
                                a[k][m]=G[k][m];
                            continue;
                        }
                        a[k][m]=max(a[k-1][m]+G[k][m],a[k][m-1]+G[k][m]);
                }
           }


           printf("%d\n",a[T][T]);

    }
   return 0;
}

問題 F: 隔離14天

題目描述
新冠肺炎疫情是新中國成立以來,在我國發生的傳播速度最快、感染範圍最廣、防控難度最大的一次重大突發公共衛生事件。偉大的中國人民在以習近平總書記爲核心的黨中央堅強領導下,採取最全面、最嚴格、最徹底的防控舉措,以巨大勇氣和強大力量,堅決阻斷全國本土疫情傳播,取得了疫情防控階段性重要成效。英雄的湖北人民、武漢人民做出了巨大的犧牲。數以萬計的逆行者,以血肉之軀和鋼鐵意志,組成了疫情防控堅不可摧的長城。“哪有什麼歲月靜好,不過是有人替你負重前行”,向所有的抗疫英雄們致敬,感謝您們!!!
爲了能夠有效切斷新冠病毒的傳播途徑,基於對新冠病毒肺炎的流行病學調查,要求所有與確診患者有過密切接觸者、有疑似症狀者、疫情期間去過疫區者或者其他當地衛生部門認爲需要隔離者需要居家隔離或者集中隔離14天。其中密切接觸者包括與確診患者乘坐同一交通工具,比如高鐵、公交、汽車等並有近距離接觸的人。
如果實施更爲嚴格的防控措施,一輛汽車上有一個確診患者或者密切接觸者,那麼該汽車上所有的人都被認爲是密切接觸者,全部需要自行居家隔離或者集中隔離14天。
現在假定編號爲0的乘客冠狀病毒核酸檢驗呈陽性,請編寫一個程序統計需隔離的總人數(包括編號爲0的乘客)。
輸入
第1行的第1個數字n表示總人數,第2個數字m表示汽車數量;從第2行開始,接下來的m行表示每輛汽車的司乘人員總人數和人員編號(人員編號是一個固定值,可以對應於我們的身份證號碼),每一行的第1個數字k表示該汽車的司乘人員總數,接下來的k個數字表示每一個人的編號。
輸出
需要被隔離的總人數。
在這裏插入圖片描述
這道題,我思維有缺陷,錯了好多,卻不明白原因,後來看了大佬做的,才慢慢試明白,太難了原因:(1):要考慮輸入全部爲0的情況,(2)初始化時,要考慮其中人數爲0時也要能初始化
在這裏插入圖片描述

方案一:

#include<bits/stdc++.h>

#define ll long long
const int N=100005;
const int INF=1000000;
using namespace std;

int p[N];//存放父節點數組
int nums[N];
int ran[N];//存放秩


void init(int x){//初始化
    p[x]=x;
    ran[x]=0;
    nums[x]=1;
}
int findp(int x){//路徑壓縮,找父節點
    if(p[x]!=x)
        p[x]=findp(p[x]);
    return p[x];
}
void merg(int x,int y){//合併2棵樹
    int a=findp(x);
    int b=findp(y);
    if(a==b)
        return ;
    if(ran[a]>ran[b]){
            p[b]=a;
        }else{
            p[a]=b;
            if(ran[a]==ran[b])
                ran[b]++;
        }


}

int main()
{

        int n,m;
        while(cin>>n>>m&&(m||n)){//出錯原因之一,排除2個都爲0的情況

        for(int i=0;i<=n;i++){//i要<=n,避免爲0時,不能初始化
            init(i);
        }
        for(int k=0;k<m;k++){
             int x,y,temp;
        cin>>x>>y;

        for(int i=1;i<x;i++){
            cin>>temp;
            merg(y,temp);
            }
        }
        int Count=0;
   for(int j=0;j<n;j++){//遍歷父節點,如果與0的父節點相同,代表曾在一輛車上
    if(findp(j)==findp(0))
        Count++;
    
        
   }
            cout<<Count<<endl;

   }


}

方案二:

#include<bits/stdc++.h>

#define ll long long
const int N=10005;
using namespace std;

int lists[N] = {0};
int p[N];
int nums[N];
int findp(int root)
{
	if(p[root]!=root)
        p[root]=findp(p[root]);
	return p[root];

}

void merg(int root1, int root2)//基本與上面差不多,只不過在合併時進行與0的父節點進行判斷,如果其中一個是,則將例外一個的數量加入到0的這一組,最後結果直接輸出0所在的數據
{
	int x; int y;
	x = findp(root1);
	y = findp(root2);
	if (x != y){

    if(x==findp(0)){
         nums[findp(0)]+=nums[y];
        p[y]=x;
    }else if(y==findp(0)){
         p[x] = y;
        nums[findp(0)]+=nums[x];
    }else{
    p[x]=y;
    nums[y]+=nums[x];
    }

	}

}


int main()
{

	int n, m;
    while(cin >> n >> m&&(m||n)){

	for (int i = 0; i <=n; i++){
        p[i] = i;
        nums[i]=1;
	}

	for (int i = 0; i < m; i++)
	{
		int a, b;
		cin >> a >> b;
		for (int j = 1; j < a; j++)
		{
			int temp;
			cin >> temp;
			merg(b, temp);
		}

	}

		cout << nums[findp(0)] << endl;
    }


}

問題 G: 最小生成樹(Kruskal)

題目描述
編程實現Kruskal算法,求圖的最小生成樹(MST)的權重。
輸入
每組數據分爲兩個部分,第一部分爲圖的點數n,和邊數m,
第二部分爲m行,每一行輸入三個數字,前兩個爲兩個頂點的編號,第三個爲邊權重。
輸出
最小生成樹的權重。
在這裏插入圖片描述
運用貪心思想,先排序,然後用並查集,判斷一條邊的2點是不是在同一顆樹上,如果不是,則加入這2點所在的邊,這樣得到的是最小生成樹

#include<bits/stdc++.h>

#define ll long long
const int N=1005;
using namespace std;

int p[N];//用來存放每個點的父節點
int ran[N];//用來存放秩,用來優化,減少樹的高度
double sm;//用來存放結果,權重
typedef struct{//定義每條邊
    int x,y;
    double v;
}Node;
Node node[N];
bool cmp(Node& a,Node& b){//升序
    return a.v<b.v;
}
void init(int x){//初始化每個節點,使每個節點的父節點爲它自己
    p[x]=x;
    ran[x]=0;
}
int findp(int x){//用來尋找父節點,用來判斷是否在同一個樹,同時使用路徑壓縮優化
    if(p[x]!=x)
        p[x]=findp(p[x]);
    return p[x];
}
bool merg(int x,int y,double v){//用來合併2棵樹,按照秩來合併,小的合併到大的上面去,並進行權值操作
    int a=findp(x);
    int b=findp(y);
    if(a==b)
        return false ;
    if(ran[a]>ran[b]){
            p[b]=a;
            sm+=v;
        }else{
            p[a]=b;
            sm+=v;
            if(ran[a]==ran[b])
                ran[b]++;
        }
        return true;

}

int main()
{

	int n, m;

    while(cin>>n>>m){
           sm=0;
                for(int i=0;i<n;i++){
                    init(i);
                }
        for(int i=0;i<m;i++){
            cin>>node[i].x>>node[i].y>>node[i].v;
        }
        sort(node,node+m,cmp);//排序
        for(int j=0;j<m;j++){
            merg(node[j].x,node[j].y,node[j].v);//並查集判斷,是否在同一棵樹上(也就是這2點是否已經存在這棵樹裏面)
        }
    printf("%.0lf\n",sm);
    }


}

問題 H: 單源最短路徑問題

題目描述
編程實現Dijkstra算法,求一個有向加權圖中,從源點出發到其他各個頂點的最短路徑。
輸入
第1行第1個值表示頂點個數,第2個值表示邊個數;第2行開始爲邊(兩個頂點,邊的起點和終點)及權重。
輸出
頂點0到每一個頂點的最短路徑長度。
在這裏插入圖片描述
跟prim算法有些類似,Dijkstra運用於帶非負權的路徑問題,首先進行初始化,將每條邊無窮大,然後輸入數據,然後初始化從0點到其他點的權值,然後從這些邊選出最小的邊,然後更新從0點到目前最小邊,到其他點的距離

#include<bits/stdc++.h>

#define ll long long
const int N=1005;
const int INF=1000000;//定義爲無窮大
using namespace std;

int mp[N][N];//存放圖
int len[N];//存放距離,權值
int used[N];//標記點是否被訪問

int main()
{

	int n, m;

    while(cin>>n>>m){
            int x,y,v;
            memset(mp,INF,sizeof(mp));

                    for(int j=0;j<m;j++){
                    cin>>x>>y>>v;
               mp[x][y] =mp[y][x]=v;
               }
        for(int i=1;i<n;i++){//初始化距離
            len[i]=mp[0][i];
        }
        len[0]=0;第一個點到自己爲0
        int k=0;
        for(int j=0;j<n;j++){
                int s=INF;
           for(int i=0;i<n;i++){//找出最小的一條邊
            if(!used[i]&&len[i]<s){
                k=i;
                s=len[i];
            }
           }
           used[k]=1;//標記已訪問
           for(int i=1;i<n;i++){//更新選點後,到其他的距離
            if(!used[i]&&(len[k]+mp[k][i])<len[i]){
                len[i]=len[k]+mp[k][i];
            }
           }
        }
        for(int i=0;i<n;i++){//打印輸出結果
             cout<<len[i]<<" ";
        }
        cout<<endl;

    }


}

問題 J: How Many Tables

今天是依納爵(Ignatius)的生日。他邀請了很多朋友。現在是晚飯時間。伊格內修斯想知道他至少需要多少張桌子。您必須注意,並不是所有的朋友都彼此認識,並且所有的朋友都不想和陌生人呆在一起。

解決此問題的一條重要規則是,如果我告訴您A認識B,而B認識C,則意味着A,B,C彼此認識,因此它們可以呆在一張桌子上。

例如:如果我告訴你A知道B,B知道C,D知道E,那麼A,B,C可以留在一個表中,而D,E必須留在另一個表中。因此,伊格納修斯至少需要2張桌子。
輸入
輸入以整數T(1 <= T <= 25)開頭,該整數表示測試用例的數量。然後是T測試用例。每個測試用例均以兩個整數N和M(1 <= N,M <= 1000)開頭。N表示朋友的數量,朋友從1到N標記。然後跟隨M行。每行包含兩個整數A和B(A!= B),這意味着朋友A和朋友B彼此認識。兩種情況之間將有一個空白行。
輸出
對於每個測試用例,只需輸出Ignatius至少需要多少個表。請勿打印任何空白。
在這裏插入圖片描述
純粹的並查集

#include<bits/stdc++.h>

#define ll long long
const int N=1005;
const int INF=1000000;
using namespace std;

int p[N];
int nums[N];
int ran[N];
int Count;

void init(int x){
    p[x]=x;
    ran[x]=0;
}
int findp(int x){
    if(p[x]!=x)
        p[x]=findp(p[x]);
    return p[x];
}
void merg(int x,int y){
    int a=findp(x);
    int b=findp(y);
    if(a==b)
        return ;
    Count--;
    if(ran[a]>ran[b]){
            p[b]=a;
        }else{
            p[a]=b;
            if(ran[a]==ran[b])
                ran[b]++;
        }


}

int main()
{
    int T;
    cin>>T;
    while(T--){
        int n,m;

        cin>>n>>m;
        Count=n;
        for(int i=0;i<n;i++){
            init(i);
        }
        int x,y;
        for(int i=0;i<m;i++){
            cin>>x>>y;
            merg(x,y);
        }
        cout<<Count<<endl;
    }


}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章