luogu P6405 [COCI2014-2015#2] ŠUMA
题面传送门
这题十分卡精度,我开到了\(10^{-12}\)才过去。
主要有两种做法。
两种做法的核心都是一样的,就是求出相邻两棵树在什么时候一样高。建权值为同样高时间的边,然后找出最大的连通块使的边权相同。
但是在找的过程中我们发现有一些树可能初始高度一样且生长速度一样,那么这些树在任何时候都相同高。在处理这一块上衍生出了两种做法。
做法一:可撤销并查集。
将任何时候都一样高的边(以下称其为万能边)最先加入并查集,然后枚举每一个出现过的天数,将同是这个天数的边加入并查集,每次找到最大连通块。
注意每一个天数枚举完要撤销。时间复杂度\(O(n^2log^2n)\)
代码实现:
#include<cstdio>
#include<algorithm>
#define d(i,j) ((i-1)*n+j)
#define esp 1e-12
#define max(a,b) ((a)>(b)?(a):(b))
#define abs(x) ((x)>0?(x):-(x))
using namespace std;
int n,m,k,ans=1,cnt,un,wn,lasttop;
struct yyy{int x,y;double z;}s[2000039];
int a[2039][2039],b[2039][2039];
inline bool cmp(yyy x,yyy y){return x.z<y.z;}
inline void swap(int &x,int &y){x^=y^=x^=y;}
struct ques{int x,y,flag;};
struct dsu{
int fa[600039],w[600039],siz[600039],sh;
ques st[600039],tmp;
inline int find(int x){return fa[x]==x?x:find(fa[x]);}
inline void merge(int x,int y){
int un=find(x),wn=find(y);
if(un!=wn){
if(w[un]<w[wn]) swap(un,wn);
st[++sh]=(ques){un,wn,w[un]==w[wn]};
siz[un]+=siz[wn];fa[wn]=un;w[un]+=(w[un]==w[wn]);
}
}
inline void del(){
tmp=st[sh--];
fa[tmp.y]=tmp.y;w[tmp.x]-=tmp.flag;siz[tmp.x]-=siz[tmp.y];
}
}f;
int main(){
freopen("1.in","r",stdin);
// freopen("suma.out","w",stdout);
register int i,j,h;
scanf("%d",&n);
for(i=1;i<=n;i++){
for(j=1;j<=n;j++) scanf("%d",&a[i][j]);
}
for(i=1;i<=n;i++){
for(j=1;j<=n;j++) scanf("%d",&b[i][j]);
}
for(i=1;i<=n*n;i++) f.fa[i]=i,f.siz[i]=f.w[i]=1;
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
if(i!=1){
if(b[i][j]==b[i-1][j]){
if(a[i][j]==a[i-1][j])f.merge(d(i,j),d(i-1,j));
}
else s[++cnt]=(yyy){d(i,j),d(i-1,j),((double)(a[i-1][j]-a[i][j]))/(b[i][j]-b[i-1][j])};
}
if(s[cnt].z<-esp) cnt--;
if(j!=1){
if(b[i][j]==b[i][j-1]){
if(a[i][j]==a[i][j-1])f.merge(d(i,j),d(i,j-1));
}
else s[++cnt]=(yyy){d(i,j),d(i,j-1),((double)(a[i][j-1]-a[i][j]))/(b[i][j]-b[i][j-1])};
}
if(s[cnt].z<-esp) cnt--;
}
}
sort(s+1,s+cnt+1,cmp);lasttop=f.sh;
for(i=1;i<=n*n;i++) ans=max(ans,f.siz[i]);
for(i=1;i<=cnt;i++){
for(j=i;j<=cnt;j++){
if(abs(s[i].z-s[j].z)>esp) break;
f.merge(s[j].x,s[j].y);
un=f.siz[f.find(s[j].x)];
ans=max(ans,un);
}
while(f.sh!=lasttop) f.del();
i=j-1;
}
printf("%d\n",ans);
}
做法二:将所有万能边加入并查集缩点,然后同样枚举天数每次进行\(dfs\)查找连通块大小。
时间复杂度\(O(n^2)\),但还没并查集跑得快。
代码实现(放一下zjdl的代码):
#include<cstdio>
#include<algorithm>
#define ll long long
#define maxn 2001
using namespace std;
int n;
struct frac{
int x,y;//x/y
frac(int x=0,int y=1):x(x),y(y){}
friend bool operator == (const frac &a,const frac &b){
return (ll)a.x*b.y==(ll)b.x*a.y;
}
friend bool operator < (const frac &a,const frac &b){
if((ll)a.y*b.y>0)return (ll)a.x*b.y-(ll)b.x*a.y<0;
else return (ll)a.x*b.y-(ll)b.x*a.y>0;
}
};
struct edges{
int u,v;
frac k;
}edge[maxn*maxn];
int len;
void add(int u,int v,frac k){
edge[++len]=(edges){u,v,k};
}
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int id(int x,int y){return (x-1)*n+y;}
int a[maxn][maxn],b[maxn][maxn];
int fa[maxn*maxn],size[maxn*maxn],ksize[maxn*maxn],w[maxn*maxn],tans;
int get(int x){return fa[x]==x?x:fa[x]=get(fa[x]);}
void merge(int x,int y){
int gx=get(x),gy=get(y);
if(gx==gy)return;
size[gy]+=size[gx];
fa[gx]=gy;
w[gy]+=w[gx];
tans=max(tans,w[gy]);
}
int ans;
bool cmp(const edges &x,const edges &y){
return x.k<y.k;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&b[i][j]);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
for(int i=1;i<=n*n;i++)fa[i]=i,size[i]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=0;k<4;k++){
int ii=i+dx[k],jj=j+dy[k];
if(a[i][j]==a[ii][jj]&&b[i][j]==b[ii][jj])merge(id(i,j),id(ii,jj));
}
}
}
for(int i=1;i<=n*n;i++)get(i),ksize[fa[i]]=size[fa[i]];
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=0;k<4;k++){
int ii=i+dx[k],jj=j+dy[k];
if(ii<1||ii>n||jj<1||jj>n)continue;
if(fa[id(i,j)]==fa[id(ii,jj)])continue;
if(a[i][j]==a[ii][jj])continue;
if((ll)(b[i][j]-b[ii][jj])*(a[ii][jj]-a[i][j])<0)continue;
add(fa[id(i,j)],fa[id(ii,jj)],frac(b[i][j]-b[ii][jj],a[ii][jj]-a[i][j]));
}
}
}
sort(edge+1,edge+1+len,cmp);
for(int i=1;i<=n*n;i++)fa[i]=i,size[i]=1,w[i]=ksize[i],ans=max(ans,w[i]);
tans=0;
int l=1;
for(int i=1;i<=len;i++){
if(i!=len&&edge[i].k==edge[i+1].k){
merge(edge[i].u,edge[i].v);
}
else{
ans=max(ans,tans);
for(int j=l;j<=i;j++){
fa[edge[j].u]=edge[j].u;
fa[edge[j].v]=edge[j].v;
size[edge[j].u]=size[edge[j].v]=1;
w[edge[j].u]=ksize[edge[j].u];
w[edge[j].v]=ksize[edge[j].v];
}
tans=0;l=i+1;
}
}
printf("%d",ans);
return 0;
}