1. 程式人生 > >USACO銀組12月月賽題解

USACO銀組12月月賽題解

USACO銀組12月月賽題解

Convention

題面

一場別開生面的牛吃草大會就要在Farmer John的農場舉辦了!
世界各地的奶牛將會到達當地的機場,前來參會並且吃草。具體地說,有N頭奶牛到達了機場(1≤N≤105),其中奶牛i在時間ti(0≤ti≤109)到達。Farmer John安排了M(1≤M≤105)輛大巴來機場接這些奶牛。每輛大巴可以乘坐C頭奶牛(1≤C≤N)。Farmer John正在機場等待奶牛們到來,並且準備安排到達的奶牛們乘坐大巴。當最後一頭乘坐某輛大巴的奶牛到達的時候,這輛大巴就可以發車了。Farmer John想要做一個優秀的主辦者,所以並不想讓奶牛們在機場等待過長的時間。如果Farmer John合理地協調這些大巴,等待時間最長的奶牛等待的時間的最小值是多少?一頭奶牛的等待時間等於她的到達時間與她乘坐的大巴的發車時間之差。
輸入保證MC≥N。

輸入格式

(檔名:convention.in):
輸入的第一行包含三個空格分隔的整數N,M,和C。第二行包含N
個空格分隔的整數,表示每頭奶牛到達的時間。

輸出格式

(檔名:convention.out):
輸出一行,包含所有到達的奶牛中的最大等待時間的最小值。

輸入樣例:

6 3 2
1 1 10 14 4 3

輸出樣例:

4
如果兩頭時間1到達的奶牛乘坐一輛巴士,時間2和時間4到達的奶牛乘坐乘坐第二輛,時間10和時間14到達的奶牛乘坐第三輛,那麼等待時間最長的奶牛等待了4個單位時間(時間10到達的奶牛從時間10等到了時間14)。

題解

  • 看到題面以後以為是擺渡車...
  • 不過根據輸出格式所說的最大的最小很容易想到二分答案。
    題目中求得是等待的時間,因此我們就對等待的時間進行二分答案即可。
  • 如何寫二分答案的判斷函式呢??
    線性掃描區間:
    如果當前奶牛之前沒有坐車,就坐一輛車,等接下來的車
    如果當前奶牛的時間與第一頭上車的奶牛時間差在mid以內,且容量夠,上車
    否則另開一輛車
    最後得到所需車的數量
  • 判定:車的數量小於m則合法
  • 時間複雜度\(O(nlogn)\)
#include<bits/stdc++.h>
using namespace std;
const int maxn=100000;
int n,m,c;
int t[maxn]={};
inline bool check(int x)
{
    int sum=0,cnt=0,last=0;
    for (int i=1;i<=n;++i)
    {
        if (t[i]-last<=x && i!=1 && sum<c) 
            sum++;//如果能乘車
        else
            last=t[i],sum=1,cnt++;//換新車
    }
    return cnt<=m;
}
int main()
{
    freopen("convention.in","r",stdin);
    freopen("convention.out","w",stdout);
    scanf("%d%d%d",&n,&m,&c);
    for (int i=1;i<=n;++i) scanf("%d",t+i);
    sort(t+1,t+n+1);//排序
    int l=0,r=1e10;
    while (l+1<r)
    {
        int mid=l+r>>1;
        if (check(mid)) r=mid;
        else l=mid;
    }//二分答案
    if (check(l)) printf("%d",l);
    else printf("%d",r);
    return 0;
} 

convention2

題面

雖然在接機上耽誤了挺長時間,Farmer John為吃草愛好牛們舉行的大會至今為止都非常順利。大會吸引了世界各地的奶牛。
然而大會的重頭戲看起來卻給Farmer John帶來了一些新的安排上的困擾。他的農場上的一塊非常小的牧草地出產一種據某些識貨的奶牛說是世界上最美味的品種的草。因此,所有參會的N頭奶牛(1≤N≤105)都想要品嚐一下這種草。由於這塊牧草地小到僅能容納一頭奶牛,這很有可能會導致排起長龍。
Farmer John知道每頭奶牛i計劃到達這塊特殊的牧草地的時間ai,以及當輪到她時,她計劃品嚐這種草花費的時間ti。當奶牛i開始吃草時,她會在離開前花費全部ti的時間,此時其他到達的奶牛需要排隊等候。如果這塊牧草地空出來的時候多頭奶牛同時在等候,那麼資歷最深的奶牛將會是下一頭品嚐鮮草的奶牛。在這裡,恰好在另一頭奶牛吃完草離開時到達的奶牛被認為是“在等待的”。類似地,如果當沒有奶牛在吃草的時候有多頭奶牛同時到達,那麼資歷最深的奶牛是下一頭吃草的奶牛。
請幫助FJ計算所有奶牛中在隊伍裡等待的時間(ai到這頭奶牛開始吃草之間的時間)的最大值。

輸入格式

檔名:convention2.in:
輸入的第一行包含N。以下N行按資歷順序給出了N頭奶牛的資訊(資歷最深的奶牛排在最前面)。每行包含一頭奶牛的ai和ti。所有的ti為不超過104的正整數,所有ai為不超過109的正整數。

輸出樣例

檔名:convention2.out:
輸出所有奶牛中的最長等待時間。

輸入樣例:

5
25 3
105 30
20 50
10 17
100 10

輸出樣例:

10
在這個例子中,我們有5頭奶牛(按輸入中的順序編號為1..5)。奶牛4最先到達(時間10),在她吃完之前(時間27)奶牛1和奶牛3都到達了。由於奶牛1擁有較深的資歷,所以她先吃,從她到達開始共計等待了2個單位時間。她在時間30結束吃草,隨後奶牛3開始吃草,從她到達開始共計等待了10單位時間。在一段沒有奶牛吃草的時間過後,奶牛5到達,在她正在吃草的時間裡奶牛2也到達了,在5個單位時間之後能夠吃到草。相比到達時間等待最久的奶牛是奶牛3。

題解

  • 到了初中以後再次受到了牛吃草問題的洗禮
  • 想到暴力模擬應該很簡單,但是時間複雜度不優秀。我們選擇資料結構維護。
  • 我們發現要求某些符合條件的最優值:即每一頭牛[在它吃完草結束前到達]的[資歷最深]的奶牛,即符合的條件是吃完草前到達,最優值是資歷最深。最優值?這不就是優先佇列嘛。因此我們可以用優先佇列來維護。
  • 具體做法是:
    暴力找出第一個吃草的牛:按照到達時間為第一關鍵字,按照資歷為第二關鍵字的順序的第一個元素。然後將其放入堆中。
    如果堆是空的:新放進去。
    如果不為空:順序查詢來到時間小於這個牛吃草結束的牛,進堆;以經驗為第一關鍵字。每一次的堆頂就是即將吃的草。
  • 預處理:預處理經驗值,第i頭可以認為是\(n-i+1\)
  • 時間複雜度:不高於\(O(nlogn)\)
  • 程式碼實現如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,ans=0;
struct node {
    int tim,exp,eat,lt;
}a[maxn];
inline bool cmp(node a,node b) {
    if (a.tim!=b.tim) return a.tim<b.tim;
    else return a.exp>b.exp;
}//排序
int main(void)
{
    freopen("convention2.in","r",stdin);
    freopen("convention2.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;++i) 
    {
        scanf("%d",&a[i].tim);
        scanf("%d",&a[i].eat);
        a[i].exp=n-i+1;
    }
    sort(a+1,a+n+1,cmp);
    priority_queue < pair< int , int > > q;
    q.push(make_pair(a[1].exp,1));//用pair型別儲存,經驗為第一關鍵字
    int now=1,last_time=a[1].tim,cnt=0;
    while (now<=n)//now表示進堆元素
    {
        while (q.size())//堆為空
        {
            int p=q.top().second;q.pop();
            ans=max(ans,last_time-a[p].tim);//更新等待時間
            while (a[now+1].tim<=last_time+a[p].eat && now<n)
            {
                now++;
                q.push(make_pair(a[now].exp,now));
            }//將結束時間前的進堆
            last_time+=a[p].eat;//last_time儲存上一頭牛結束吃草的時間
        }
        now++,last_time=a[now].tim;
        q.push(make_pair(a[now].exp,now));//新進堆.如果now到n了也不要緊,下一層反正會退出
    }
    cout<<ans<<endl;
    return 0;
} 

Mooyo Mooyo

題面

由於手上(更確實的,蹄子上)有大把的空餘時間,Farmer John的農場裡的奶牛經常玩電子遊戲消磨時光。她們最愛的遊戲之一是基於一款流行的電子遊戲Puyo Puyo的奶牛版;名稱當然叫做Mooyo Mooyo。
Mooyo Mooyo是在一塊又高又窄的棋盤上進行的遊戲,高\(N\)\(1 \leq N \leq 100\))格,寬10格。 這是一個\(N = 6\)的棋盤的例子:
0000000000
0000000300
0054000300
1054502230
2211122220
1111111223
每個格子或者是空的(用0表示),或者是九種顏色之一的乾草捆(用字元1..9表示)。重力會使得乾草捆下落,所以沒有乾草捆的下方是0。
如果兩個格子水平或垂直方向直接相鄰,並且為同一種非0顏色,那麼這兩個格子就屬於同一個連通區域。任意時刻出現至少\(K\)個格子構成的連通區域,其中的乾草捆就會全部消失,變為0。如果同時出現多個這樣的連通區域,它們同時消失。隨後,重力可能會導致乾草捆向下落入某個變為0的格子。由此形成的新的佈局中,又可能出現至少\(K\)個格子構成的連通區域。若如此,它們同樣也會消失(如果又有多個這樣的區域,則同時消失),然後重力又會使得剩下的方塊下落,這一過程持續進行,直到不存在大小至少為\(K\)的連通區域為止。
給定一塊Mooyo Mooyo棋盤的狀態,輸出這些過程發生之後最終的棋盤的圖案。

輸入格式

檔名:mooyomooyo.in:
輸入的第一行包含\(N\)\(K\)\(1 \leq K \leq 10N\))。以下\(N\)行給出了棋盤的初始狀態。

輸出格式

檔名:mooyomooyo.out:
輸出\(N\)行,描述最終的棋盤狀態。

輸入樣例:

6 3
0000000000
0000000300
0054000300
1054502230
2211122220
1111111223

輸出樣例:

0000000000
0000000000
0000000000
0000000000
1054000000
2254500000

在上面的例子中,如果K=3
,那麼存在一個大小至少為K

的顏色1的連通區域,同樣有一個顏色2的連通區域。當它們同時被移除之後,棋盤暫時成為了這樣:

0000000000
0000000300
0054000300
1054500030
2200000000
0000000003

然後,由於重力效果,乾草捆下落形成這樣的佈局:

0000000000
0000000000
0000000000
0000000000
1054000300
2254500333

再一次地,出現了一個大小至少為K

地連通區域(顏色3)。移除這個區域就得到了最終的棋盤佈局:

0000000000
0000000000
0000000000
0000000000
1054000000
2254500000

題解

  • 大模擬...碼農題...頭很大啊...
    我們暫且叫它俄羅斯方塊消消樂
  • 這道題分為3部:判斷存在能消的方塊→消掉方塊→下落
    每一個都必須與dfs(flood-fiil)來模擬,因為方格的形狀是不固定的。
  • 第一步:暴力求聯通塊,順便求出和每一個聯通塊所在的聯通塊大小。有≥k的就繼續,否則就結束迴圈。搞個死迴圈就好,讓電腦慢慢搞
    第二部:消方塊。根據第一步求出的數字,≥k的搞成0即可。
    第三部:下落。按照行數倒序列舉。最下那麼先列舉到的一定是最底層的,疊在最小面並且從下往上疊。至於怎麼疊?用一個數組標記即可。
  • 對於程式碼的解釋:搜尋的程式碼佔了大部分,本質相同,因為具體操作不同所以分開來謝了。用函式寫應該還是比較直觀的了吧!
  • 程式碼如下:
#include<bits/stdc++.h>
using namespace std;
int n,k;
int vd[1000];
int f[101][11];
int v[101][11];
int u[101][11];
int nm[101][11];
char a[101][11];
char temp[101][11];
int dx[4]={1,-1,0,0};
int dy[4]={0,0,-1,1};
int num(int x,int y)
{
    int sum=1;
    v[x][y]=1;
    for (int i=0;i<4;++i)
    {
        int nx=x+dx[i],ny=y+dy[i];
        if (nx>=1 && nx<=n && ny>=1 && ny<=10)
        if (!v[nx][ny] && a[x][y]==a[nx][ny]) sum+=num(nx,ny);
    }
    return sum;
}
//求x,y所在連通塊大訊息 
void cover(int x,int y)
{
    u[x][y]=1;
    for (int i=0;i<4;++i)
    {
        int nx=x+dx[i],ny=y+dy[i];
        if (nx>=1 && nx<=n && ny>=1 && ny<=10)
        if (!u[nx][ny] && a[x][y]==a[nx][ny])
        {
            nm[nx][ny]=nm[x][y];
            cover(nx,ny);
        }
    }
    return;
}
//求出某一個節點所在連通塊大小後不斷覆蓋,節約時間 
bool many(void)
{
    memset(v,0,sizeof(v));
    memset(u,0,sizeof(u));
    for (int i=1;i<=n;++i)
        for (int j=1;j<=10;++j)
            if (nm[i][j]==0) 
            {
                nm[i][j]=num(i,j);
                cover(i,j);
            }
    for (int i=1;i<=n;++i)
        for (int j=1;j<=10;++j)
            if (nm[i][j]>=k && a[i][j]!='0') return true;
    return false;
}
//判斷要不要繼續消下去 
void die(int x,int y)
{
    v[x][y]=1;
    char now=a[x][y];
    a[x][y]='0';
    for (int i=0;i<4;++i)
    {
        int nx=x+dx[i],ny=y+dy[i];
        if (nx>=1 && nx<=n && ny>=1 && ny<=10)
        if (!v[nx][ny] && now==a[nx][ny]) die(nx,ny);
    }
}
//消成0 
void noit(void)
{
    for (int i=1;i<=n;++i)
        for (int j=1;j<=10;++j)
            if (a[i][j]!='0' && nm[i][j]>=k) 
                die(i,j);
    return;
}
//列舉消的座標 
void downit(void)
{
    memset(temp,'0',sizeof(temp));
    for (int i=1;i<=100;++i) vd[i]=n+1;
    //掉落位置的預處理 
    for (int i=n;i;--i)
        for (int j=1;j<11;++j)
            if (a[i][j]>'0') vd[j]--,temp[vd[j]][j]=a[i][j];
    //temp用來暫時儲存影象 
    for (int i=1;i<=n;++i)
        for (int j=1;j<11;++j)
            a[i][j]=temp[i][j];
    return;
} 
//掉落 
void print(void)
{
    for (int i=1;i<=n;++i)
        for (int j=1;j<=10;++j)
            if (j==10) 
            {
                if (i==n) cout<<a[i][j];
                else cout<<a[i][j]<<'\n';
            }
            else cout<<a[i][j];
    cout<<endl;
}
//列印結果 
int main(void)
{
    freopen("mooyomooyo.in","r",stdin);
    freopen("mooyomooyo.out","w",stdout);
    cin>>n>>k;
    for (int i=1;i<=n;++i) 
        for (int j=1;j<=10;++j)
            cin>>a[i][j];
    while (many()==true) 
    {
        memset(v,0,sizeof(v));
        memset(u,0,sizeof(u));
        noit();
        downit();
        memset(nm,0,sizeof(nm));
    }
    print();
    return 0;
}