洛谷 P6405 [COCI 2014/2015 #2] ŠUMA 题解

Solution

不难发现我们只需要关心相邻的格子在什么时候相等,仅在相等时连边,然后找出现过的最大连通块。

设两个相邻格子分别为 \((i_1,j_1)\)\((i_2,j_2)\),相等时刻为 \(x\)。不难列出方程 \((v_{i_2,j_2}-v_{i_1,j_1})x=h_{i_1,j_1}-h_{i_2,j_2}\)

\(A=h_{i_1,j_1}-h_{i_2,j_2}\)\(B=v_{i_2,j_2}-v_{i_1,j_1}\)

  1. \(A=0\)\(B=0\),则两个格子高度永远相等,连边是永久边。
  2. \(A\neq0\)\(B=0\),无解。
  3. \(B\neq 0\)\(x\) 有唯一解,因此边是瞬时边。注意 \(x\) 必须 \(\ge 0\)

我们先处理出所有边,建一个并查集维护永久边形成的连通块并缩成点。再用第二个并查集维护这些新点之上的连通块。

然后将所有瞬时边按出现时刻排序后,每次将所有同一时刻出现的边加入第二个并查集并更新答案即可。但这样需要多次初始化第二个并查集,这可以用时间戳+懒标记 \(O(1)\) 实现。

浮点数可能会掉精度,最好手写分数存储时刻。

Code

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i(a);i<b;++i)
#define rept(i,a,b) for(int i(a);i<=b;++i)
#define int long long
#define il inline
using namespace std;
constexpr int N=701,S=N*N,dx[2]={0,1},dy[2]={1,0};
struct Frac{
    int a,b;
    Frac(int _a=0,int _b=1):a(_a),b(_b){
        int g=__gcd(a,b);
        a/=g,b/=g;
        if(b<0) a=-a,b=-b;
    }
    il bool operator<(const Frac &rhs) const {
        return a*rhs.b<rhs.a*b;
    }
};
struct Edge{
    int u,v;
    Frac t;
    il bool operator<(const Edge &rhs) const {
        return t<rhs.t;
    }
}e[S<<1];
int h[N][N],v[N][N];
int f1[S],s1[S];  // 第一个并查集
int f2[S],l2[S],s2[S],stamp;  // 第二个并查集
// l2:f2上一次被访问的时间
// stamp:当前时间戳(懒标记)
int n,m,ans;
int find1(int x){
    return x==f1[x]?x:f1[x]=find1(f1[x]);
}
int find2(int x){
    if(l2[x]<stamp){
        l2[x]=stamp;
        f2[x]=x;
        s2[x]=s1[x];
    }
    return x==f2[x]?x:f2[x]=find2(f2[x]);
}
il int calc(int i,int j){
    return n*(i-1)+j;
}
void solve(int l,int r){
    // e[l]~e[r]为出现在同一个时刻的边
    ++stamp;
    rept(i,l,r){
        int u=find2(find1(e[i].u)),v=find2(find1(e[i].v));
        if(u^v){
            f2[u]=v;
            s2[v]+=s2[u];
            ans=max(ans,s2[v]);
        }
    }
}
signed main(){
    cin.tie(0)->sync_with_stdio(0);
    cin>>n;
    rept(i,1,n) rept(j,1,n) cin>>h[i][j];
    rept(i,1,n) rept(j,1,n) cin>>v[i][j];
    rept(i,1,n*n) f1[i]=i,s1[i]=1;
    rept(i,1,n){
        rept(j,1,n){
            rep(d,0,2){
                int di=i+dx[d];
                int dj=j+dy[d];
                int A=h[i][j]-h[di][dj];
                int B=v[di][dj]-v[i][j];
                if(!A&&!B){  // 永久边
                    int u=find1(calc(i,j)),v=find1(calc(di,dj));
                    if(u^v){
                        f1[u]=v;
                        s1[v]+=s1[u];
                        ans=max(ans,s1[v]);
                    }
                }else if(B){  // 瞬时边
                    Frac t(A,B);
                    if(t.a>=0) e[++m]={calc(i,j),calc(di,dj),t};
                }
            }
        }
    }
    sort(e+1,e+m+1);
    int l=1,r=1;
    for(int l=1,r=1;r<=m;){
        if(r==m||e[r].t<e[r+1].t){
            solve(l,r);
            l=r+1;
        }
        ++r;
    }
    cout<<ans;
    return 0;
}
posted @ 2026-01-17 13:41  xiaoniu142857  阅读(3)  评论(0)    收藏  举报