算法競賽進階指南0x08 總結練習(上)

飛行員兄弟

“飛行員兄弟”這個遊戲,需要玩家順利的打開一個擁有16個把手的冰箱。

已知每個把手可以處於以下兩種狀態之一:打開或關閉。

只有當所有把手都打開時,冰箱纔會打開。

把手可以表示爲一個4х4的矩陣,您可以改變任何一個位置[i,j]上把手的狀態。

但是,這也會使得第i行和第j列上的所有把手的狀態也隨着改變。

請你求出打開冰箱所需的切換把手的次數最小值是多少。

輸入格式
輸入一共包含四行,每行包含四個把手的初始狀態。

符號“+”表示把手處於閉合狀態,而符號“-”表示把手處於打開狀態。

至少一個手柄的初始狀態是關閉的。

輸出格式
第一行輸出一個整數N,表示所需的最小切換把手次數。

接下來N行描述切換順序,每行輸入兩個整數,代表被切換狀態的把手的行號和列號,數字之間用空格隔開。

數據範圍
1≤i,j≤4
輸入樣例:

-+--
----
----
-+--

輸出樣例:
6
1 1
1 3
1 4
4 1
4 3
4 4
注意:如果存在多種打開冰箱的方式,則按照優先級整體從上到下,同行從左到右打開。

非常簡單直接暴力就可以了,沒什麼好說的

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

#define pii pair<int,int>

using namespace std;

const int N=10;

int f;
int change[N][N];

inline int get(int x,int y)
{
    return 4*x+y;
}

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    
    for(int i=0;i<4;i++)
    {
        char str[N];
        scanf("%s",str);
        for(int j=0;j<4;j++)
            if(str[j]=='+')
                f|=1<<get(i,j);
    }

    for(int i=0;i<4;i++)
        for(int j=0;j<4;j++)
        {
            for(int k=0;k<4;k++)
            {
                change[i][j]|=1<<get(i,k);
                change[i][j]|=1<<get(k,j);
            }
        }

    int t=f;
    vector<pii> res,temp;
    for(int i=0;i<1<<16;i++)
    {
        temp.clear();
        f=t;
        for(int j=0;j<16;j++)
            if(i>>j&1)
            {
                f^=change[j/4][j%4];
                temp.push_back({j/4,j%4});
            }
        if(!f&&(res.empty()||temp.size()<res.size()))
            res=temp;
    }
    cout<<res.size()<<endl;
    for(auto t:res)
        cout<<t.first+1<<" "<<t.second+1<<endl; 
    


    return 0;
}

占卜DIY

達達學會了使用撲克DIY占卜。

方法如下:

一副去掉大小王的撲克共52張,打亂後均分爲13堆,編號1~13,每堆4張,其中第13堆稱作“生命牌”,也就是說你有4條命。

這裏邊,4張K被稱作死神。

初始狀態下,所有的牌背面朝上扣下。

流程如下:

1.抽取生命牌中的最上面一張(第一張)。

2.把這張牌翻開,正面朝上,放到牌上的數字所對應編號的堆的最上邊。(例如抽到2,正面朝上放到第2堆牌最上面,又比如抽到J,放到第11堆牌最上邊,注意是正面朝上放)

3.從剛放了牌的那一堆最底下(最後一張)抽取一張牌,重複第2步。(例如你上次抽了2,放到了第二堆頂部,現在抽第二堆最後一張發現是8,又放到第8堆頂部…)

4.在抽牌過程中如果抽到K,則稱死了一條命,就扔掉K再從第1步開始。

5.當發現四條命都死了以後,統計現在每堆牌上邊正面朝上的牌的數目,只要同一數字的牌出現4張正面朝上的牌(比如4個A),則稱“開了一對”,當然4個K是不算的。

6.統計一共開了多少對,開了0對稱作”極兇”,12對爲“大凶”,3對爲“兇”,45對爲“小兇”,6對爲“中庸”,78對“小吉”,9對爲“吉”,1011爲“大吉”,12爲“滿堂開花,極吉”。

輸入格式
一共輸入13行數據,每行四個數字或字母,表示每堆牌的具體牌型(不區分花色只區分數字),每堆輸入的順序爲從上到下。

爲了便於讀入,用0代表10。

同行數字用空格隔開。

輸出格式
輸出一個整數,代表統計得到的開出的總對數。

輸入樣例:
8 5 A A
K 5 3 2
9 6 0 6
3 4 3 4
3 4 4 5
5 6 7 6
8 7 7 7
9 9 8 8
9 0 0 0
K J J J
Q A Q K
J Q 2 2
A K Q 2
輸出樣例:
9

直接模擬。。~ 。~沒什麼好講的。。。。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;

const int N=15;

int have[N];
vector<int> card[N];

inline int get(char ch)
{
    if(ch=='0') return 10;
    else if(ch=='J') return 11;
    else if(ch=='Q') return 12;
    else if(ch=='K') return 13;
    else if(ch=='A') return 1;
    else return ch-'0';
}

int main(){
    //freopen("data.in","r",stdin);
    freopen("data.out","w",stdout);
    for(int i=1;i<=13;i++)
    {
        char str[2];
        for(int j=1;j<=4;j++)
        {
            scanf("%s",str);
            card[i].push_back(get(str[0]));
        }
    }

    while(card[13].size())
    {
        int get=card[13].back();
        card[13].pop_back();
        while(get!=13)
        {
            have[get]++;
            
            int temp=card[get].back();
            card[get].pop_back();
            get=temp;
        }
    }

    int res=0;
    for(int i=1;i<=12;i++)
        if(have[i]==4)
            res++;
    cout<<res<<endl;

    return 0;
}

分形

分形,具有以非整數維形式充填空間的形態特徵。

通常被定義爲“一個粗糙或零碎的幾何形狀,可以分成數個部分,且每一部分都(至少近似地)是整體縮小後的形狀”,即具有自相似的性質。

現在,定義“盒子分形”如下:

一級盒子分形:

   X

二級盒子分形:

   X X
    X
   X X

如果用B(n - 1)代表第n-1級盒子分形,那麼第n級盒子分形即爲:

  B(n - 1)        B(n - 1)

          B(n - 1)

  B(n - 1)        B(n - 1)

你的任務是繪製一個n級的盒子分形。

輸入格式
輸入包含幾個測試用例。

輸入的每一行包含一個不大於7的正整數n,代表要輸出的盒子分形的等級。

輸入的最後一行爲-1,代表輸入結束。

輸出格式
對於每個測試用例,使用“X”符號輸出對應等級的盒子分形。

請注意’X’是一個大寫字母。

每個測試用例後輸出一個獨立一行的短劃線。

輸入樣例:

1
2
3
4
-1

輸出樣例

X
-
X X
 X
X X
-
X X   X X
 X     X
X X   X X
   X X
    X
   X X
X X   X X
 X     X
X X   X X
-
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
   X X               X X
    X                 X
   X X               X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
         X X   X X
          X     X
         X X   X X
            X X
             X
            X X
         X X   X X
          X     X
         X X   X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
   X X               X X
    X                 X
   X X               X X
X X   X X         X X   X X
 X     X           X     X
X X   X X         X X   X X
-

這題還是比較右意思的,用到了分形思想,要解決當前問題由5個子問題構成,還是比較容易的,來看代碼。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>

using namespace std;

const int N=5050;

char g[N][N];

void dfs(int n)
{
    if(n==1)
    {
        g[0][0]='X';
        return ;
    }
    dfs(n-1);
    int len=pow(3,n-2);
    for(int i=0;i<len;i++)
        for(int j=0;j<len;j++)
        {
            g[i+2*len][j]=g[i][j];
            g[i+len][j+len]=g[i][j];
            g[i+2*len][j+2*len]=g[i][j];
            g[i][j+2*len]=g[i][j];
        }

}

int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);

    dfs(7);
    int n;
    while(cin>>n,~n)
    {
        int len=pow(3,n-1);
        for(int i=0;i<len;i++)
        {
            for(int j=0;j<len;j++)
            {
                if(g[i][j]=='X') printf("X");
                else printf(" ");
            }
            puts("");
        }
        puts("-");
    }

    return 0;
}

襲擊

在與聯盟的戰鬥中屢戰屢敗後,帝國撤退到了最後一個據點。

依靠其強大的防禦系統,帝國擊退了聯盟的六波猛烈進攻。

經過幾天的苦思冥想,聯盟將軍亞瑟終於注意到帝國防禦系統唯一的弱點就是能源供應。

該系統由N個核電站供應能源,其中任何一個被摧毀都會使防禦系統失效。

將軍派出了N個特工進入據點之中,打算對能源站展開一次突襲。

不幸的是,由於受到了帝國空軍的襲擊,他們未能降落在預期位置。

作爲一名經驗豐富的將軍,亞瑟很快意識到他需要重新安排突襲計劃。

他現在最想知道的事情就是哪個特工距離其中任意一個發電站的距離最短。

你能幫他算出來這最短的距離是多少嗎?

輸入格式
輸入中包含多組測試用例。

第一行輸入整數T,代表測試用例的數量。

對於每個測試用例,第一行輸入整數N。

接下來N行,每行輸入兩個整數X和Y,代表每個核電站的位置的X,Y座標。

在接下來N行,每行輸入兩個整數X和Y,代表每名特工的位置的X,Y座標。

輸出格式
每個測試用例,輸出一個最短距離值,結果保留三位小數。

每個輸出結果佔一行。

數據範圍
1≤N≤100000,
0≤X,Y≤1000000000
輸入樣例:
2
4
0 0
0 1
1 0
1 1
2 2
2 3
3 2
3 3
4
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
輸出樣例:
1.414
0.000

這是一道比較經典的一題,很容易想到的一種方式就是暴力逐一比較,但這個數據範圍就勸退,105 ,O(n2)一定會TLE。我們要想一種O(nlogn)的方法。其實我們可以用歸併+貪心優化成O(nlogn)。
如果這個問題使一維的,我們很容易想到先排序,然後再用O(n)的方法找到最小值。在這裏插入圖片描述
我們一定會這樣挨個比較:(1,2)(2,3)(3,4)(4,5)(5,6)
從這幾組數中選一個最小值,因爲和顯然(1,3)一定會大於(1,2),那麼我們能再二維中尋找出相同的性質嗎?當然可以

我們先對行進行排序,然後分治,發現滿足最小值的點就這幾種而已。
在這裏插入圖片描述
每一組內的距離最小的點,相鄰兩組內最小的點,最小的兩個點一定使從這兩種情況中選一個更小的。如果用歸併的思想第一種情況都不需要我們處理,我們可以把它交給遞歸處理,那第二種怎麼辦呢?我們可以用遞歸先處理出兩塊的最小值,然後我們暴力枚舉這樣一個正方形裏的點。正方形的邊長爲剛剛遞歸處理的最小值。這樣我們這題就結束了。
在這裏插入圖片描述
雖然這個正方形裏的點是要用N2處理的但是一般裏面的點稀疏。
提問:這種做法有沒有問題呢?當然有了~ 。~
如果正方形裏的點過多一樣會TLE,但是非常少見,一般不會出現這樣的情況(特殊情況:出題人特意卡我們)那怎麼辦呢?出題人出招了,我們也要有相應的應對策略,我們可以將所有點旋轉一個隨機的角度,做一個預處理,這樣他們就很難卡我們了。
提問:那有沒有真正完美的nlogn的方法呢?有的,大家可以上網搜索我連那個算法的名字都忘了hh
一般情況下我們這個做法已經很完美了~ .~我們來看代碼。。。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>

using namespace std;

const int N=200010;
const double INF=1e10;
struct Point{
    int x,y;
    int owner;
    bool operator<(const Point &a) const{
        return x<a.x;
    }
};

Point point[N],t1[N];

double dict(const Point &a,const Point &b)
{
    if(a.owner==b.owner) return INF;
    double dx=1.0*a.x-b.x;
    double dy=1.0*a.y-b.y;
    return sqrt(dx*dx+dy*dy);
}

double merge(int l,int r)
{
    if(l>=r) return INF;
    int mid=l+r>>1;
    int mid_x=point[mid].x;
    double res=min(merge(l,mid),merge(mid+1,r));
    {
        int i=l,j=mid+1;
        for(int k=l;k<=r;k++)
        { 
            if(j>r||(i<=mid&&(point[i].y<point[j].y))) t1[k]=point[i++];
            else t1[k]=point[j++];
        }
        for(int k=l;k<=r;k++)
            point[k]=t1[k];
    }
    int k=0;
    for(int i=l;i<=r;i++)
        if(point[i].x>mid_x-res&&point[i].x<mid_x+res)
            t1[k++]=point[i];
    for(int i=0;i<k;i++)
        for(int j=i-1;j>=0&&t1[i].y-t1[j].y<res;j--)
            res=min(res,dict(t1[i],t1[j]));

    return res;
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("data.out","w",stdout);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%d%d",&point[i].x,&point[i].y),point[i].owner=1;
        for(int i=n;i<2*n;i++)
            scanf("%d%d",&point[i].x,&point[i].y),point[i].owner=2;
        sort(point,point+2*n);
        printf("%.3lf\n",merge(0,2*n-1));
    }

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