大一下第四周ACM實驗課題解

省四弱鸡發表於2024-03-28

7-1 ACM宣傳
作者 杜祥軍
單位 青島大學
LB大神想組織集訓隊去學校各處宣傳ACM,但是大神不想讓隊員們走太多路,因此想寫程式碼計算一下,到各地宣傳再回到博知401的最短路徑總和是多少。
已知:學校一共有n個宣傳點,博知401是標號為1的點。剩下n-1個點每個點各派1位隊員,詢問每個隊員到達宣傳點再回到博知401的最短路徑和是多少。

輸入格式:
輸入由T個案例組成。輸入的第一行只包含正整數T。
接下來是N和M,1 <= N,M <= 1000000,表示N個點和連線N個點的M條邊。
然後有M行,每行包括三個值U,V,W,表示從點U到點V需要W的路程。你可以假設該圖連通。

輸出格式:
對於每個案例,列印一行,表示隊員們從博知401出發到其他點再回到博知401的路徑總和的最小值。

輸入樣例:
2
2 2
1 2 13
2 1 33
4 6
1 2 10
2 1 60
1 3 20
3 4 10
2 4 5
4 1 50
輸出樣例:
46
210
本題使用了單源最短路的想法
我使用了dijkstra演算法來解決該問題
板子地址:https://www.luogu.com.cn/problem/P4779
首先從簡單的開始想 每個隊員從1返回到各自的位置 那麼就是一個簡單的dijkstra就能解決
現在再考慮每個隊員從自己的位置到達1的位置的最短路 那麼只需要將每個邊都反轉 然後從1出發即為每個點到達1的最短路
所以需要跑兩遍 dijkstra
簡單地思考即可得出為正邊的時候跑一遍
建立返圖地時候再跑一遍
建立返圖時給每個點+n
code:

點選檢視程式碼
const int maxn=1e6+10;
int n,m,cnt;
int dis[maxn],h[maxn],to[maxn],val[maxn],nxt[maxn],vis[maxn];
struct node{
    int v,w;
    friend bool operator <(node a,node b){
        return a.w>b.w;
    }
};
priority_queue<node>q;
void add(int a,int b,int c){
    to[++cnt]=b;
    val[cnt]=c;
    nxt[cnt]=h[a];
    h[a]=cnt;
}
void end(){
    for(int i=0;i<=max(n<<1,m<<1);i++)to[i]=val[i]=h[i]=nxt[i]=0;
    for(int i=0;i<=n<<1;i++)vis[i]=0;
}
void dijkstra(int s){
    for(int i=1;i<=n<<1;i++)dis[i]=1e18;
    dis[s]=0;
    node tmp;
    tmp.v=s;tmp.w=0;q.push(tmp);
    while(!q.empty()){
        int u=q.top().v;q.pop();
        if(vis[u])continue;vis[u]=1;
        for(int i=h[u];i;i=nxt[i]){
            if(dis[to[i]]>dis[u]+val[i]){
                dis[to[i]]=dis[u]+val[i];
                tmp.w=dis[to[i]];
                tmp.v=to[i];
                q.push(tmp);
            }
        }
    }
}
void solve(){
    cnt=0;
    cin>>n>>m;
    for(int i=1,u,v,w;i<=m;i++){
        cin>>u>>v>>w;
        add(u,v,w);
        add(v+n,u+n,w);
    }
    dijkstra(1);
    int ans=0;
    for(int i=2;i<=n;i++){
        ans+=dis[i];
    }
    dijkstra(1+n);
    for(int i=2+n;i<=n<<1;i++)ans+=dis[i];
    cout<<ans<<'\n';
    end();
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    int _=1;
    cin>>_;
    while(_--)solve();
}
7-2 征服遊戲 作者 朱允剛 單位 吉林大學 Conquer是一款桌面遊戲,在這個遊戲裡,幾個相互對立的玩家要滅掉對方的國家。遊戲板上分成很多假設的國家。當輪到一個玩家時,一個國家的軍隊只能攻擊邊界相連的敵對國家。在征服一個國家之後,軍隊就可以移動到剛被征服的國家。在遊戲期間,玩家通常要征服一連串的國家,以便把大量軍隊從起始國家轉移到目標國家。通常,玩家在選擇要征服路徑上的國家時,儘量使要征服的國家數量最少。假定遊戲板上有n個國家,編號為0至n-1。

請編寫程式,對於給定的起始國家和目標國家,計算到達目標國家至少需要征服多少個國家,不必輸出征服的國家序列,只輸出征服的國家數量即可。例如下圖所示的國家地圖,從起始國家0到目標國家4,最少需征服2個國家。

輸入格式:
輸入第一行為3個整數n、e和m,均不超過1000。n表示國家數。接下來e行表示國家間的接壤情況,每行為兩個整數i和j,表示國家i與國家j接壤。接下來m行表示m組查詢,每行為兩個整數x和y,表示起始國家和目標國家編號。

輸出格式:
輸出為m行,即對於給定的x和y,需要征服國家的最少數量。

輸入樣例:
5 6 2
0 1
0 3
1 2
2 3
1 4
2 4
0 4
1 2

輸出樣例:
2
1
只需按照樣例建圖
然後每次測試時情況vis
以x為起點 輸出dis[y]的值即可
code:

點選檢視程式碼
void solve(){
    int q;
    cin>>n>>m>>q;
    for(int i=1,u,v;i<=m;i++){
        cin>>u>>v;
        add(u+1,v+1,1);
        add(v+1,u+1,1);
    }
    while(q--){
        int x,y;cin>>x>>y;
        for(int i=1;i<=n;i++)vis[i]=0;
        dijkstra(x+1);
        cout<<dis[y+1]<<'\n';
    }
}//上題已經給出dijkstra和建立邊的方式後面給出的code將不再顯示dijkstra和建邊
7-3 單源最短路徑 作者 朱允剛 單位 吉林大學 請編寫程式求給定正權有向圖的單源最短路徑長度。圖中包含n個頂點,編號為0至n-1,以頂點0作為源點。

輸入格式:
輸入第一行為兩個正整數n和e,分別表示圖的頂點數和邊數,其中n不超過20000,e不超過1000。接下來e行表示每條邊的資訊,每行為3個非負整數a、b、c,其中a和b表示該邊的端點編號,c表示權值。各邊並非按端點編號順序排列。

輸出格式:
輸出為一行整數,為按頂點編號順序排列的源點0到各頂點的最短路徑長度(不含源點到源點),每個整數後一個空格。如源點到某頂點無最短路徑,則不輸出該條路徑長度。

輸入樣例:
4 4
0 1 1
0 3 1
1 3 1
2 0 1
輸出樣例:
1 1
板子題 注意每個數字後面一個空格 作者被空格殺了一發
code:

點選檢視程式碼
void solve(){
    cin>>n>>m;
    for(int i=1,u,v,w;i<=m;i++){
        cin>>u>>v>>w;
        add(u+1,v+1,w);
    }
    dijkstra(1);
    vector<int>ans;
    for(int i=2;i<=n;i++){
        if(dis[i]!=1e18)ans.pb(dis[i]);
    }
    for(int i=0;i<ans.size();i++)cout<<ans[i]<<" ";
}

7-4 哈利·波特的考試
作者 陳越
單位 浙江大學
哈利·波特要考試了,他需要你的幫助。這門課學的是用魔咒將一種動物變成另一種動物的本事。例如將貓變成老鼠的魔咒是haha,將老鼠變成魚的魔咒是hehe等等。反方向變化的魔咒就是簡單地將原來的魔咒倒過來唸,例如ahah可以將老鼠變成貓。另外,如果想把貓變成魚,可以透過念一個直接魔咒lalala,也可以將貓變老鼠、老鼠變魚的魔咒連起來唸:hahahehe。

現在哈利·波特的手裡有一本教材,裡面列出了所有的變形魔咒和能變的動物。老師允許他自己帶一隻動物去考場,要考察他把這隻動物變成任意一隻指定動物的本事。於是他來問你:帶什麼動物去可以讓最難變的那種動物(即該動物變為哈利·波特自己帶去的動物所需要的魔咒最長)需要的魔咒最短?例如:如果只有貓、鼠、魚,則顯然哈利·波特應該帶鼠去,因為鼠變成另外兩種動物都只需要念4個字元;而如果帶貓去,則至少需要念6個字元才能把貓變成魚;同理,帶魚去也不是最好的選擇。

輸入格式:
輸入說明:輸入第1行給出兩個正整數N (≤100)和M,其中N是考試涉及的動物總數,M是用於直接變形的魔咒條數。為簡單起見,我們將動物按1~N編號。隨後M行,每行給出了3個正整數,分別是兩種動物的編號、以及它們之間變形需要的魔咒的長度(≤100),數字之間用空格分隔。

輸出格式:
輸出哈利·波特應該帶去考場的動物的編號、以及最長的變形魔咒的長度,中間以空格分隔。如果只帶1只動物是不可能完成所有變形要求的,則輸出0。如果有若干只動物都可以備選,則輸出編號最小的那隻。

輸入樣例:
6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80
輸出樣例:
4 70
本題可以用johnson(Om(n2))或者flody(On3)來做
但是沒有給出m範圍所以使用flody
flody運用了類似動態規劃的思想
其中dis[i,j,k]表示從i-j只經過1-k內的點
對其進行分類討論 1從i-j只經過編號為1-k-1的點的路徑 那麼其長度最短顯然為dis[i,j,k-1]
從2 從i到k再從k到j那麼長度為dis[i,k,k-1]+dis[k,j,k-1]
對其取min
其中因為每次轉移可以把第三位的k最佳化掉
所以為節省空間考慮將轉移方程最佳化為min(dis[i][j],dis[i][k]+dis[k][j])
其中本題想法即為列舉每個點到其他點的最大值中最小的那個
code:

點選檢視程式碼
const int maxn=105;
int n,m,cnt;
int dis[maxn][maxn];
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)
            if(i==j)dis[i][i]=0;
            else dis[i][j]=1e18;
    for(int i=1;i<=m;i++){
        int x,y,val;cin>>x>>y>>val;
        dis[y][x]=dis[x][y]=min(dis[x][y],val);
    }

    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
            }
        }
    }
    int mi=1e18,pos;
    for(int i=1;i<=n;i++){
        int mx=0;
        for(int j=1;j<=n;j++){
            mx=max(dis[i][j],mx);
        }
        if(mx<mi){
            mi=mx;
            pos=i;
        }
        if(mx<mi){
            mi=mx;
            pos=i;
        }
    }
    if(mi==1e18){
        cout<<0<<'\n';return;
    }
    cout<<pos<<" "<<mi<<'\n';
}

我認為7-5和7-6本質一樣 所以放一塊講
而且這兩道題又長又臭 寫的時候非常難受

7-5 最少頂點最短路
作者 朱允剛
單位 吉林大學
給定一個正權有向圖,圖中包含n個頂點,編號為0至n-1。以頂點0作為源點,程式設計序求頂點0到各頂點的最短路徑。若頂點0到某頂點存在多條最短路徑,則輸出經過頂點最少的那條路徑,例如圖1,0到4的最短路徑有兩條,0→1→2→4和0→3→4,權值和均為6,但0→3→4經過頂點個數最少,故為所求。輸入資料保證對每個頂點最多隻有一條滿足上述條件的路徑。

GRAPH.png

輸入格式:
輸入第一行為兩個正整數n和e,分別表示圖的頂點數和邊數,其中n不超過15000,e不超過1000。接下來e行表示每條邊的資訊,每行為3個非負整數a、b、c,其中a和b表示該邊的端點編號,c表示權值。各邊並非按端點編號順序排列。

輸出格式:
輸出為若干行由“->”間隔的數字序列,每行為源點0到某頂點的滿足條件的最短路徑,如源點到某頂點無最短路徑,則不輸出該條路徑。各條路徑按終點的遞增順序輸出,源點到源點的最短路徑無需輸出。

輸入樣例:
5 5
0 1 1
0 3 4
1 2 2
2 4 3
3 4 2
輸出樣例:
0->1
0->1->2
0->3
0->3->4
7-6 最少點字典序最短路徑
作者 朱允剛
單位 吉林大學
給定一個正權有向圖,圖中包含n個頂點,編號為0至n-1。以頂點0作為源點,請編寫程式求頂點0到各頂點的最短路徑。若頂點0到某頂點存在多條最短路徑,則輸出經過頂點最少的那條路徑,例如圖1(a)中0到4的經過頂點最少的最短路徑為0 - 3 - 4。若存在多條最短路徑且其經過頂點個數相等,則輸出字典序最小者。例如圖1(b)中0到5的滿足條件的最短路徑為0 - 2 - 5。

g.jpg

注:字典序,即物件在字典中的順序。對於兩個數字序列,從第一個數字開始比較,當某一個位置的數字不同時,該位置數字較小的序列,字典序較小,例如1 2 3 9比1 2 4 5小,1 2 8 9比1 2 10 3小。

輸入格式:
輸入第一行為兩個正整數n和e,分別表示圖的頂點數和邊數,其中n不超過20000,e不超過20000。接下來e行表示每條邊的資訊,每行為3個非負整數a、b、c,其中a和b表示該邊的端點編號,c表示權值。各邊並非按端點編號順序排列。

輸出格式:
輸出為若干行由“->”間隔的數字序列,每行為源點0到某頂點的滿足條件的最短路徑,如源點到某頂點無最短路徑,則不輸出該條路徑。各條路徑按終點的遞增順序輸出,源點到源點的最短路徑無需輸出。

輸入樣例:
6 7
0 1 1
1 4 2
4 5 3
0 3 4
3 5 2
0 2 5
2 5 1

輸出樣例:
0->1
0->2
0->3
0->1->4
0->2->5
首先建立depth來表示每個點的最小深度
建立pre來表示每個點的前驅節點
to[i] 表示前往的節點

點選檢視程式碼
const int maxn=1e6+10;
int n,m,cnt;
int dis[maxn],h[maxn],to[maxn],val[maxn],nxt[maxn],vis[maxn];
struct node{
    int v,w;
    friend bool operator <(node a,node b){
        if(a.w==b.w)return a.v>b.v;//本次如果插入很多個相同深度的節點那麼節點大小升序
        return a.w>b.w;
    }
};
vector<int>v[20005];
int pre[20005],depth[20005];
priority_queue<node>q;
void add(int a,int b,int c){
    to[++cnt]=b;
    val[cnt]=c;
    nxt[cnt]=h[a];
    h[a]=cnt;
}//連結串列向前星建立圖
void dijkstra(int s){
    for(int i=0;i<=n;i++)dis[i]=0x3f;
    for(int i=0;i<=n;i++)pre[i]=0x3f;
    dis[s]=0;
    node tmp;
    tmp.v=s;tmp.w=0;q.push(tmp);
    while(!q.empty()){
        int u=q.top().v;q.pop();
        if(vis[u])continue;vis[u]=1;
        for(int i=h[u];i;i=nxt[i]){
            if(dis[to[i]]>dis[u]+val[i]){
                dis[to[i]]=dis[u]+val[i];
                tmp.w=dis[to[i]];
                tmp.v=to[i];
                depth[to[i]]=depth[u]+1;
                pre[to[i]]=u;
                q.push(tmp);
            }
            else if(dis[to[i]]==dis[u]+val[i]){
                if(depth[u]+1<depth[to[i]]){
                    depth[to[i]]=depth[u]+1;
                    pre[to[i]]=u;
                    q.push(node{to[i],dis[to[i]]});
                }else if(depth[u]+1==depth[to[i]]){
                    int a=u;
                    int b=pre[to[i]];
                    while(pre[a]!=pre[b]){
                        a=pre[a],b=pre[b];
                    }
                    if(a<b){
                        pre[to[i]]=u;
                        q.push(node{to[i],dis[to[i]]});
                    }
                }
            }
        }
    }
}
void solve(){
    cin>>n>>m;
    for(int i=1,u,t,w;i<=m;i++){
        cin>>u>>t>>w;
        add(u+1,t+1,w);
    }//透過+1防止越界出錯
    dijkstra(1);
    for(int i=2;i<=n;i++){
        int j=i;
        while(j!=1){//如果不到第一個節點不結束
            if(pre[j]==0x3f)break;//如果找不到前繼節點就結束 該種情況為不連通
            v[i].pb(j);
            j=pre[j];
        }
    }
    for(int i=2;i<=n;i++){
        if(v[i].size()){
            cout<<0;
            for(int j=v[i].size()-1;j>=0;j--){
                cout<<"->"<<v[i][j]-1;
            }
            cout<<'\n';
        }
    }
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    int _=1;
//    cin>>_;
    while(_--)solve();
}
7-7 堅持散步 作者 黃龍軍 單位 紹興文理學院 住在南山校區的HY喜歡散步。他發現南山校區有n個景點(從1到n進行編號)很值得觀賞,比如竹林舞步,小河夕陽等。這些景點中,有些相互能夠直達,而有些要先經過其他的一些景點才能到達。他已經記下了一些直達道路的用時資訊。散步是好的,但散步太久也會累的,所以當他身處某個景點時,就想知道從這個景點散步到另一個他想去的景點的最少用時。

輸入格式:
首先輸入一個正整數T,表示測試資料的組數,然後是T組測試資料。
每組測試包含兩個部分。分別是: 景點資訊 和 查詢。

景點資訊 部分,首先是2個整數n和m(2≤n≤100,1≤m≤n(n-1)/2),分別表示有n個景點,以及m條直達道路的用時資訊。然後有m行輸入,每行輸入三個整數a,b,c(1≤a,b≤n,a≠b,1≤c<100),表示由景點a散步到景點b,HY散步需要用時c分鐘,當然,他由景點b散步到景點a也要c分鐘。

查詢 部分,首先是1個整數k(1<=k<=5000),表示後面接k次詢問。緊接著k行資料,每行包括兩個整數s,d(1≤s,d≤n,s≠d),分別表示HY想知道從景點s散步到另一個景點d的用時(分鐘數)。

注意,因為HY有點粗心,可能發生以下情況:
(1)他所記下有些用時資訊可能重複,比如:
1 2 3
2 1 3
(2)有些同一條路的用時資訊可能不一致,比如:
1 2 3
1 2 4
在這種情況下,以其中用時最少的資訊為準。
(3)有些路用時資訊可能忘記了,比如有3個景點1,2,3;他只記住1條路的用時:
1 2 1
在這種情況下,認為從景點1到景點3沒有直達的路,從景點2到景點3也沒有直達的路。

輸出格式:
對於每組測試,每次詢問輸出一行,包含一個整數,表示從景點s散步到另一個景點d的最少用時。如果景點s到景點d無路可通,輸出-1。

輸入樣例:
2
3 2
1 2 3
2 3 1
3
1 2
1 3
3 2
3 2
1 2 3
2 1 2
3
1 2
1 3
2 3
輸出樣例:
3
4
1
2
-1
-1
非常簡單的一道flody板子題
不多解釋
code:

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define all(x) x.begin(),x.end()
#define rall(x) x.rbegin(),x.rend()
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int mod=1e9+7;
int gcd(int a,int b){return b?gcd(b,a%b):a;};
int qpw(int a,int b){int ans=1;while(b){if(b&1)ans=ans*a%mod;a=a*a%mod,b>>=1;}return ans;}
int inv(int x){return qpw(x,mod-2);}
const int maxn=105;
int n,m,cnt;
int dis[maxn][maxn];
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)
            if(i==j)dis[i][i]=0;
            else dis[i][j]=1e18;
    for(int i=1;i<=m;i++){
        int x,y,val;cin>>x>>y>>val;
        dis[y][x]=dis[x][y]=min(dis[x][y],val);
    }
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
            }
        }
    }
    int q;cin>>q;
    while(q--){
        int s,d;cin>>s>>d;
        if(dis[s][d]==1e18)cout<<-1<<'\n';
        else cout<<dis[s][d]<<'\n';
    }
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
    int _=1;
    cin>>_;
    while(_--)solve();
}

相關文章