1. 程式人生 > >牛客網 2018年全國多校演算法寒假訓練營練習比賽(第四場) 題解

牛客網 2018年全國多校演算法寒假訓練營練習比賽(第四場) 題解

A-石油採集

題目描述
隨著海上運輸石油洩漏的問題,一個新的有利可圖的行業正在誕生,那就是撇油行業。如今,在墨西哥灣漂浮的大量石油,吸引了許多商人的目光。這些商人們有一種特殊的飛機,可以一瓢略過整個海面20米乘10米這麼大的長方形。(上下相鄰或者左右相鄰的格子,不能斜著來)當然,這要求一瓢撇過去的全部是油,如果一瓢裡面有油有水的話,那就毫無意義了,資源完全無法利用。現在,商人想要知道,在這片區域中,他可以最多得到多少瓢油。
地圖是一個N×N的網路,每個格子表示10m×10m的正方形區域,每個區域都被標示上了是油還是水
連結:https://www.nowcoder.net/acm/contest/76/A


題解
遍歷所有點,判斷當前點能不能和周圍某個點相消。例如

樣例一
###
#..
###
樣例二
###
#..
#..
#..

判斷某點能否相消,就要看與其周圍直線連線的點是否是#。
可以證明的是,永遠是從最開頭消點,例如樣例二。最好的方案是(0,0)(1,0)、(0,1)(0,2)、(2,0)(3,0)。當我們判斷(0,0)能否與其周圍點相消時,先要dfs走到這個方向的開頭,反過來消。

程式碼

#include <bits/stdc++.h>
using namespace std;
int n,ans;
char mp[55][55];
const int dir[4][2
]={{-1,0},{1,0},{0,-1},{0,1}}; bool dfs(int x,int y) { for(int i=0;i<4;i++){ int dx=x+dir[i][0]; int dy=y+dir[i][1]; if(dx<n&&dx>=0&&dy<n&&dy>=0&&mp[dx][dy]=='#'){ mp[x][y]='.'; if(!dfs(dx,dy)){ mp[dx][dy]='.'
; ans++; return true; } mp[x][y]='#'; } } return false; } int main() { int T; while(~scanf("%d",&T)){ for(int cas=1;cas<=T;cas++){ ans=0; scanf("%d",&n); for(int i=0;i<n;i++) scanf("%s",mp[i]); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(mp[i][j]=='#'){dfs(i,j);} } } printf("Case %d: %d\n",cas,ans); }} }

B-道路建設

題目描述
隨著如今社會的不斷變化,交通問題也變得越來越重要,所以市長決定建設一些公路來方便各個城市之間的貿易和交易。雖然市長的想法很好,但是他也遇到了一般人也經常頭疼的問題,那就是手頭的經費有限……在規劃過程中,設計師們已經預算出部分城市之間建設公路的經費需求。現在市長想知道,它能不能將他的m個城市在有限的經費內實現公路交通。如果可以的話,輸出Yes,否則輸出No(兩個城市不一定要直接的公路相連,間接公路到達也可以。)
連結:https://www.nowcoder.net/acm/contest/76/B
備註:兩個城市之間可能存在多條線路
題解
最小生成樹。這裡我用的Kruskal演算法。
程式碼

#include <bits/stdc++.h>
using namespace std;
struct node
{
    int v1,v2;
    int h;
    bool operator<(const node&other)const{
        return h<other.h;
    }
}nod[10005];
int main()
{
    int c,n,m;
    while(~scanf("%d%d%d",&c,&n,&m)){
        int ans=0;
        for(int i=0;i<n;i++){//輸入兩點及兩點路徑。
            scanf("%d%d%d",&nod[i].v1,&nod[i].v2,&nod[i].h);
        }
        sort(nod,nod+n);//給兩點間的路徑排序。優先選擇最小。
        bool vis[105]={0};
        vis[nod[0].v1]=1;//預設先選擇nod[0].x為起始點。
        int NN=1;//已連通的點數
        for(int i=0;i<n;i++)
        {
            int num=vis[nod[i].v1]+vis[nod[i].v2];
            if(NN==m) break;//若所有點都連通則退出。
            if(num==1){
                ans+=nod[i].h;
                NN++;
                vis[nod[i].v1]=vis[nod[i].v2]=1;
                i=0;//重新遍歷未連通的最小路徑的點。
            }
        }
        printf("%s\n",ans<=c?"Yes":"No");
    }
    return 0;
}

C-求交集

題目描述
給你兩個升序排列的集合,求出兩個集合的交集。
連結:https://www.nowcoder.net/acm/contest/76/C
題解
先把集合A用陣列V存起來。再用二分搜尋確定集合B中的某個數是否在集合A中,若在即輸出。
程式碼

#include <bits/stdc++.h>
using namespace std;
vector<int>v;
const int inf=1000000001;
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        for(int i=0;i<n;i++){
            int t;scanf("%d",&t);
            v.push_back(t);
        }
        int tem=inf;
        for(int i=0;i<m;i++){
            int t;scanf("%d",&t);
            if(binary_search(v.begin(),v.end(),t)==true){
                if(tem!=inf) printf("%d ",tem);
                tem=t;
            }
        }
        if(tem!=inf) printf("%d\n",tem);
        else printf("empty\n");
    }
    return 0;
}

E-通知小弟

題目描述
在戰爭時期,A國派出了許多間諜到其他國家去收集情報。因為間諜需要隱祕自己的身份,所以他們之間只是單向聯絡。所以,某個間諜只能單向聯絡到一部分的間諜。同時,間諜也不知道跟他聯絡的是誰。
HA是間諜們的老大,但他也只能聯絡到部分的間諜。HA現在有一項命令有告訴所有的間諜。HA想要知道他至少要告訴多少個他能聯絡上的間諜才能通知到所有的間諜。
連結:https://www.nowcoder.net/acm/contest/76/E
題解
題目意思是求你最少需要聯絡幾個你能聯絡的小弟。那麼就容易出現考慮不到位的情況。比如HA能聯絡間諜A和B,B也能聯絡A,這種情況只要聯絡B即可。具體看我另外給出的測試樣例。
樣例一

3 3
1 2 3
0
2 1 3
0

樣例二

4 2
1 2
0
1 3
1 4
1 2

程式碼

#include <bits/stdc++.h>
using namespace std;
vector<int>x;
vector<int>a[505];
bool vis[505],cst[505];
int ans=0;
void dfs(int k,int s)
{
    vis[k]=1;
    for(int i=0;i<a[k].size();i++){
        int v=a[k][i];
        if(!vis[v]) dfs(v,s);
        else if(v!=s &&cst[v]){
            cst[v]=false;
            ans--;
        }
    }
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=n;i++) a[i].clear();
        x.clear();
        ans=0;
        for(int i=1;i<=m;i++){
            int xx;scanf("%d",&xx);
            x.push_back(xx);
        }
        for(int i=1;i<=n;i++){
            int nn;scanf("%d",&nn);
            for(int j=1;j<=nn;j++){
                int aa;scanf("%d",&aa);
                a[i].push_back(aa);
            }
        }
        for(int i=0;i<m;i++){
            if(!vis[x[i]]){
                ans++;
                cst[x[i]]=true;
                dfs(x[i],x[i]);
            }
        }
        int flag=1;
        for(int i=1;i<=n;i++){
            if(!vis[i]){flag=0;break;}
        }
        printf("%d\n",flag?ans:-1);
    }
    return 0;
}

F-Call to your teacher

題目描述
從實驗室出來後,你忽然發現你居然把自己的電腦落在了實驗室裡,但是實驗室的老師已經把大門鎖上了。更糟的是,你沒有那個老師的電話號碼。你開始給你知道的所有人打電話,詢問他們有沒有老師的電話,如果沒有,他們也會問自己的同學來詢問電話號碼。那麼,你能聯絡到老師並且拿到電腦嗎。
連結:https://www.nowcoder.net/acm/contest/76/F
題解
dfs搜尋即可。注意設定vis[]陣列,免去多餘的操作。否則在OJ上提示記憶體超限。
程式碼

#include <bits/stdc++.h>
using namespace std;
vector<vector<int> > son;
int n,m;
int flag=0;
bool vis[55];
void dfs(int k)
{
    vis[k]=1;
    if(k==n){flag=1;return;}
    for(int i=0;i<son[k].size();i++){
        if(flag==1) return;
        if(vis[son[k][i]]==0) dfs(son[k][i]);
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        flag=0;
        while(!son.empty()) son.clear();
        memset(vis,0,sizeof(vis));
        son.resize(n+1);
        for(int i=1;i<=m;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            son[x].push_back(y);
        }
        dfs(1);
        printf("%s\n",(flag==1?"Yes":"No"));
    }
}

H-老子的全排列呢

題目描述
老李見和尚贏了自己的酒,但是自己還捨不得,所以就耍起了賴皮,對和尚說,光武不行,再來點文的,你給我說出來1-8的全排序,我就讓你喝,這次絕不耍你,你能幫幫和尚麼?
連結:https://www.nowcoder.net/acm/contest/76/H
題解
主要用到了一個函式next_permutation。next_permutation函式詳析<<
或者用全排列的方法。子集生成樣題<<
程式碼

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int a[10]={1,2,3,4,5,6,7,8};
    do{
            for(int i=0;i<8;i++){
                cout<<a[i];
                if(i==7) cout<<endl;
                else cout<<" ";
            }
    }while(next_permutation(a,a+8));
    return 0;
}