問題描述
十滴水是一個非常經典的小遊戲。
小 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≤𝑐≤109,1≤𝑚≤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 }