P11593 [NordicOI 2024] Thin Ice 题解 / 贪心
题目传送门:P11593 [NordicOI 2024] Thin Ice。
显然,我们不可能记录行走的路径,因为我们可以不拾取金币而在满足承受限制下,我们发现这有点类似连通块内可以随意移动。
这个东西显然拥有单调性,考虑二分,相当于判读一开始在边缘有 \(mid\) 个金币,然后在满足承受限度下移动,对于每个位置可以放下一个金币或者不操作,最后是否可以走到边缘且没有金币。
根据贪心,我们将承受限制按照大到小排序,然后依次加入矩阵中,对于每个连通块维护从边缘来的最小金币数设为 \(w_u\),假设当前加入的承受限度为 \(x\),那么所有 \(w_u>x\) 的连通块直接倒闭了,因为无法通过这里,这直接将 \(w_u\) 设为 \(\inf\),然后假设加入这个位置之后合并了 \(u,v\) 两个连通块,那么合并后的连通块的 \(w\) 值即为 \(\min\{\max\{0,w_u-sz_v\},\max\{0,w_v-sz_u\}\}\),其中 \(sz_u\) 表示连通块的大小,这个式子相当于是一个连通块的最优方案跑到另外一个连通块的每个位置放下金币。
使用并查集维护一下这个过程即可,最后判断是否合法即为整块的 \(w\) 是否为 \(0\)。
#include<bits/stdc++.h>
#define int long long
#define double long double
using namespace std;
const int N=2e5+10;
int w[N],n,m,d[N],f[N],sz[N];
inline int read(){
char c=getchar();
int f=1,ans=0;
while(c<48||c>57) f=(c==45?f=-1:1),c=getchar();
while(c>=48&&c<=57) ans=(ans<<1)+(ans<<3)+(c^48),c=getchar();
return ans*f;
}
inline int get(int i,int j){return (i-1)*m+j;}
struct node{
int d,x,y;
bool operator <(const node &xx) const{return d>xx.d;}
}a[N];
int dx[]={1,0,-1,0},dy[]={0,1,0,-1};
int find(int x){
if (f[x]==x) return x;
return f[x]=find(f[x]);
}
inline bool check(int mid){
for (int i=1;i<=n*m;i++) w[i]=1e18,f[i]=i,sz[i]=1;
for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) if (i==1||i==n||j==1||j==m) if (d[get(i,j)]>=mid) w[get(i,j)]=mid-1;
for (int i=1;i<=n*m;i++){
int d=a[i].d,x=a[i].x,y=a[i].y;
for (int j=0;j<4;j++){
int x1=x+dx[j],y1=y+dy[j];
if (x1<1||x1>n||y1<1||y1>m||::d[get(x1,y1)]<d) continue;
int u=find(get(x,y)),v=find(get(x1,y1));
if (u==v) continue;
w[u]=min(w[u]>d?1000000000000000000ll:max(0ll,w[u]-sz[v]),w[v]>d?1000000000000000000ll:max(0ll,w[v]-sz[u]));
sz[u]+=sz[v];
f[v]=u;
}
}
for (int i=1;i<=n*m;i++) if (w[i]==0) return 1;
return 0;
}
main(){
n=read(),m=read();
for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) d[get(i,j)]=read(),a[get(i,j)]={d[get(i,j)],i,j};
sort(a+1,a+n*m+1);
int l=1,r=n*m,ans=0;
while(l<=r){
int mid=l+r>>1;
if (check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
cout <<ans;
return 0;
}

浙公网安备 33010602011771号