借鉴自:【CCF-CSP】202403-4 十滴水-CSDN博客
题目描述
十滴水是一个非常经典的小游戏。
小 C 正在玩一个一维版本的十滴水游戏。我们通过一个例子描述游戏的基本规则。
游戏在一个 1×c 的网格上进行,格子用整数x(1≤x≤c) 编号,编号从左往右依次递增。网格内 m 个格子里有 1∼41∼4 滴水,其余格子里没有水。在我们的例子中,c=m=5,按照编号顺序,每个格子中分别有 2,4,4,4,22,4,4,4,2 滴水。
玩家可以进行若干次操作,每次操作中,玩家选择一个有水的格子,将格子的水滴数加一。任何时刻若某个格子的水滴数大于等于 55,这个格子里的水滴就会向两侧爆开。此时,这个格子的水被清空,同时对于左方、右方两个方向同时进行以下操作:找到当前格子在对应方向上最近的有水的格子,如果存在这样的格子,将这个格子的水滴数加一。若在某个时刻,有多个格子的水滴数大于等于 55,则最靠左的先爆开。
在我们的例子中,若玩家对第三格进行操作,则其水滴数变为 55,故第三格水滴爆开,水被清空,其左侧最近的有水格子(第二格)和右侧最近的有水格子(第四格)的水量增加 11,此时每个格子中分别有 2,5,0,5,22,5,0,5,2 滴水。
此时第二格和第四格的水滴数均大于等于 55,按照规则,第二格的水先爆开,爆开后每个格子中分别有 3,0,0,6,23,0,0,6,2 滴水;最后第四格的水滴爆开,每个格子中分别有 4,0,0,0,34,0,0,0,3 滴水。
小 C 开始了一局游戏并进行了 n 次操作。小 C 在每次操作后,会等到所有水滴数大于等于 55 的格子里的水滴都爆开再进行下一次操作。
小 C 想知道他的水平有多高,于是他想知道每一次操作后还有多少格子里有水。
题解:
#include<bits/stdc++.h>
#define int long long
#define up(l,r,i) for(int i=l,END##i=r;i<=END##i;++i)
#define dn(r,l,i) for(int i=r,END##i=l;i>=END##i;--i)
using namespace std;
#define IOS ios::sync_with_stdio("false");cin.tie(0);cout.tie(0);
#define freopen freopen("IN.in","r",stdin);freopen("OUT.out","w",stdout);
typedef pair<int,int> PII;
const int N=3e5+10;
const int INF=0x7ffffff;
struct node
{
int p,w;
int pre,nex;
bool operator< (const node & oth) const{
return p<oth.p;
}
}a[N];
map<int,int> idx;
bool vis[N];
signed main(){
IOS
int c,m,n;
cin>>c>>m>>n;
int ans=m;
for(int i=1;i<=m;i++){
cin>>a[i].p>>a[i].w;
}
sort(a+1,a+m+1);
for(int i=1;i<=m;i++){
a[i].pre=i-1;
a[i].nex=i+1;
idx[a[i].p]=i;
}
priority_queue<int,vector<int>,greater<int> > q;
for(int i=1;i<=n;i++){
int x;
cin>>x;
int id=idx[x];
a[id].w+=1;
if(a[id].w>=5&&!vis[id]){
q.push(id);
vis[id]=true;
}
while(!q.empty()){
ans--;
id=q.top();
q.pop();
int pre=a[id].pre,nxt=a[id].nex;
a[pre].nex=nxt;
a[nxt].pre=pre;
if(pre>=1){
a[pre].w+=1;
if(a[pre].w>=5&&!vis[pre]){
q.push(pre);
vis[pre]=true;
}
}
if(nxt<=m){
a[nxt].w+=1;
if(a[nxt].w>=5&&!vis[nxt]){
q.push(nxt);
vis[nxt]=true;
}
}
}
cout<<ans<<endl;
}
return 0;
}
这道题难点在于点的范围是1e9的,但是仅有3e5+5个点。因此可以使用离散化来描述点的信息。每个点可以用位置,水滴数以及左边最近有水的格子(前驱pre),右边最近有水的格子(后驱(nex))来描述。每次操作之后将会爆开的点存入优先队列当中,由于是左边的点会先爆开,因此使用小根堆来存即可。