【csp202403-4】十滴水【第33次CCF計算機軟體能力認證】

SAKURA12發表於2024-05-26

問題描述

十滴水是一個非常經典的小遊戲。

小 C 正在玩一個一維版本的十滴水遊戲。我們透過一個例子描述遊戲的基本規則。

遊戲在一個1×𝑐的網格上進行,格子用整數𝑥(1≤𝑥≤𝑐)編號,編號從左往右依次遞增。網格內𝑚個格子裡有1∼4滴水,其餘格子裡沒有水。在我們的例子中,𝑐=𝑚=5,按照編號順序,每個格子中分別有2,4,4,4,2滴水。

玩家可以進行若干次操作,每次操作中,玩家選擇一個有水的格子,將格子的水滴數加一。任何時刻若某個格子的水滴數大於等於5,這個格子裡的水滴就會向兩側爆開。此時,這個格子的水被清空,同時對於左方、右方兩個方向同時進行以下操作:找到當前格子在對應方向上最近的有水的格子,如果存在這樣的格子,將這個格子的水滴數加一。若在某個時刻,有多個格子的水滴數大於等於5,則最靠左的先爆開。

在我們的例子中,若玩家對第三格進行操作,則其水滴數變為5,故第三格水滴爆開,水被清空,其左側最近的有水格子(第二格)和右側最近的有水格子(第四格)的水量增加1,此時每個格子中分別有2,5,0,5,2滴水。

此時第二格和第四格的水滴數均大於等於5,按照規則,第二格的水先爆開,爆開後每個格子中分別有3,0,0,6,2滴水;最後第四格的水滴爆開,每個格子中分別有4,0,0,0,3滴水。

小C開始了一局遊戲並進行了𝑛次操作。小C在每次操作後,會等到所有水滴數大於等於5的格子裡的水滴都爆開再進行下一次操作。

小 C 想知道他的水平有多高,於是他想知道每一次操作後還有多少格子裡有水。

保證這𝑛次操作都是合法的,即每次操作時操作的格子裡都有水。

輸入格式

從標準輸入讀入資料。

輸入的第一行三個整數𝑐,𝑚,𝑛分別表示網格寬度、有水的格子個數以及操作次數。

接下來𝑚行每行兩個整數𝑥,𝑤表示第𝑥格有𝑤滴水。

接下來𝑛行每行一個整數𝑝表示小C對第𝑝格做了一次操作。

輸出格式

輸出到標準輸出。

輸出𝑛行,每行一個整數表示這次操作之後網格上有水的格子數量。

樣例輸入

5 5 2
1 2
2 4
3 4
4 4
5 2
3
1

樣例輸出

2
1

資料範圍

對於所有測試資料,

  • 1≤𝑐≤1091≤𝑚≤min⁡(𝑐,3×105)1≤𝑛≤4𝑚
  • 1≤𝑥,𝑝≤𝑐1≤𝑤≤4
  • 輸入的所有𝑥兩兩不同;
  • 對於每個輸入的𝑝,保證在對應操作時𝑝內有水。

特殊性質:在遊戲的任意時刻(包括水滴爆開的連鎖反應過程中),只有至多一個格子的水滴數大於等於5

題解

由於格子總數c達到109,因此直接儲存每個格子的狀態是不太可能的;注意到有水的格子數m只有3×105,考慮只儲存有水的格子。

題目並沒有保證輸入的格子是有序的,保險起見讀入後先排個序,方便後續處理。

一個格子爆開後,只會影響到兩邊最近的有水的格子,如果有水的格子分佈比較稀疏,前後遍歷每個格子去找有水的格子的話,可能會因為遍歷了較多空格子而花費比較多的時間。

考慮用連結串列來儲存有水的格子,則一個格子爆開後影響到的就是其前繼節點和後繼節點,只需O(1)的時間就可以找到被影響到的節點,一個格子爆開後,格子裡的水清零,則將這個格子從連結串列中刪除。

一個格子爆開後,可能導致其影響到的格子也爆開,即一個格子爆開可能導致多個格子同時爆開,但由於爆開順序是從左往右,因此較早被影響到的格子不一定較早爆開。例如以下狀態:4,4,4,4,2,玩家先對第三格進行操作,第三格爆開後最先影響到的是第二格和第四格,接著第二個爆開影響到第一格和第四格,此時,雖然第四格比第一格先被影響到,但是因為第一格的位置更靠左,所以第一格會比第四格更早爆開。

考慮用優先佇列儲存即將爆開的格子,每次取出位置最靠左的格子,令其爆開,然後檢查被影響到的格子,如果達到爆開條件,就加入佇列。直到佇列為空。

對於每次操作後有水的格子的統計,我們知道最初有水的格子總數,每當有一個格子爆開,有水的格子就少一個,因此我們只需要在操作的過程中統計爆開的格子數,不需要每次重新數一遍有水的格子

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <vector>
 4 #include <queue>
 5 #include <map>
 6 #define pa pair<int,int>
 7 using namespace std;
 8 int c,m,n,p,cnt;
 9 struct node{
10     int x,w,pre,nex;
11 }a[300005];
12 map<int,int> mp; // 標記i號格子在陣列a中的下標 
13 priority_queue<int,vector<int>,greater<int> > q;
14 bool cmp(node x,node y)
15 {
16     return x.x<y.x;
17 }
18 int main()
19 {
20     int i,j,x,w;
21     scanf("%d%d%d",&c,&m,&n);
22     for (i=1;i<=m;i++)
23       scanf("%d%d",&a[i].x,&a[i].w);
24     sort(a+1,a+m+1,cmp);
25     for (i=1;i<=m;i++)
26       mp[a[i].x]=i,
27       a[i].pre=i-1,
28       a[i].nex=i+1;
29     cnt=m; // 當前有水的格子數 
30     int pre,nex;
31     while (n--)
32     {
33         scanf("%d",&p);
34         i=mp[p]; // 取出格子在a陣列中的位置 
35         a[i].w++;
36         if (a[i].w>=5) 
37           q.push(i);
38         // 達到爆開條件的格子先不處理
39         // 放進佇列後面同一處理 
40         while (!q.empty())
41         {
42             // 取出位置最靠左的格子 
43             i=q.top();
44             q.pop();
45             if (!a[i].w) continue;
46             // 一個格子可能被影響多次,但只會爆開一次
47             // 第一次爆開後格子裡的水就清零了
48             // 透過判斷格子裡是否有水來判斷格子是否爆開過 
49             pre=a[i].pre;
50             nex=a[i].nex;
51             // 爆開格子
52             cnt--;// 有水的格子數減一 
53             a[i].w=0;// 清空格子裡的水
54             //影響連結串列裡相鄰的格子 
55             a[pre].w++;
56             a[nex].w++;
57             // 從連結串列中刪除格子 
58             a[pre].nex=nex;
59             a[nex].pre=pre;
60             // 檢驗被影響的格子會不會爆開 
61             if (pre>0 && a[pre].w>=5)
62               q.push(pre);
63             if (nex<=m && a[nex].w>=5)
64               q.push(nex);
65         }
66         printf("%d\n",cnt);
67     }
68     return 0;
69 }

相關文章