原题链接:点这里
很明显,瓦西亚会取当前能够拆的方块中编号最大的一个,皮台亚会取当前能够拆的方块中编号最小的一个。
证明很简单,以瓦西亚为例,若瓦西亚不取最大的一个,就算后面全取最大的也抵不过当前与最优解的差距。
那么,我们可以维护一个\(set\),存放当前所有能够拆的方块的编号。
初始,我们把所有能拆的方块加入集合。每次,我们取集合中编号最大/最小的方块,累加答案,然后寻找它左下/下方/右下方的方块,看这三个方块能否被拆掉。若能,则加入至集合中。这样依次处理,直到集合为空即可。
两个要点:
- 如何判断当前方块能否被拆?枚举它左上/上方/右上方的方块,看去掉当前方块后,这三个方块还是否平衡即可。
- 由于一些原因,集合中编号最大/最小的方块不一定是合法的。我们要不停地取编号最大/最小的方块,直到当前方块合法才行。
Code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MOD=1e9+9,N=100010;
struct Node{
int x,y,id;
bool operator<(const Node& a)const{return id<a.id;}
};
unordered_map<ll,int> vis;
set<Node> s;
int m,sx[N],sy[N];
ll ans;
inline ll mp(int x,int y){//当前点坐标的编号
return (ll)x*998244853+y;
}
inline bool judge(int x,int y){//判断当前方块能否被拆
for(int i=-1;i<=1;i++){
int xx=x+i,yy=y+1,cnt=0;
if(!yy||vis.find(mp(xx,yy))==vis.end()) continue;
for(int j=-1;j<=1;j++){
if(vis.find(mp(xx+j,yy-1))!=vis.end()) cnt++;
}
if(cnt<2) return false;
}
return true;
}
int main(){
scanf("%d",&m);
for(int i=0;i<m;i++) scanf("%d%d",&sx[i],&sy[i]),vis[mp(sx[i],sy[i])]=i;
for(int i=0;i<m;i++){
if(judge(sx[i],sy[i])) s.insert((Node){sx[i],sy[i],i});
}
while(!s.empty()){
set<Node>::iterator it;Node x;
while(!s.empty()){
it=s.end();it--;
x=*it;s.erase(it);
if(judge(x.x,x.y)) break;
}
ans=((ans*m)%MOD+x.id)%MOD;
cout<<x.id<<endl;
vis.erase(mp(x.x,x.y));
for(int i=-1;i<=1;i++){
int xx=x.x+i,yy=x.y-1;
if(yy<0||vis.find(mp(xx,yy))==vis.end()) continue;
if(judge(xx,yy)) s.insert((Node){xx,yy,vis[mp(xx,yy)]});
}
if(s.empty()) break;
while(!s.empty()){
it=s.begin();
x=*it;s.erase(it);
if(judge(x.x,x.y)) break;
}
ans=((ans*m)%MOD+x.id)%MOD;
cout<<x.id<<endl;
vis.erase(mp(x.x,x.y));
for(int i=-1;i<=1;i++){
int xx=x.x+i,yy=x.y-1;
if(yy<0||vis.find(mp(xx,yy))==vis.end()) continue;
if(judge(xx,yy)) s.insert((Node){xx,yy,vis[mp(xx,yy)]});
}
}
cout<<ans<<endl;
return 0;
}