在sub2中,我们使用并查集维护每一个颜色相同连续段。
在并查集的根部存储当前的颜色和连续段的左/右端点。
每次尝试拓展一下。
在sub3中,根据sub2的启发,我们也维护连通块使得相邻的连通块颜色不同。
在修改时,如果我们成功把当前点修改成另一个颜色,则当前点的所有相邻点都会和这个点合并。
维护一个点的所有相邻点可以启发式合并。
注意到如果我们对一个连通块(x)的所有连向的连通块(y)进行合并,则(x->y)的边都没用了,可以被删除。
这个删除操作保证了时间复杂度,使每条边只会被遍历1次。
时间复杂度(RSlog_2{RS})。
48分代码(没调完)
#include<bits/stdc++.h>
using namespace std;
#define N 200010
int a,b,l[N],r[N],v[N],p[N],q,t[N],c[N],tx[4]={1,0,-1,0},ty[4]={0,-1,0,1},tp,vi[N];
int fd(int x){
return !p[x]?x:p[x]=fd(p[x]);
}
int id(int x,int y){
return (x-1)*b+y;
}
struct no{
int x,y;
}st[N];
queue<no>qu;
unordered_map<int,int>ma[N];
void mg(unordered_map<int,int> &x,unordered_map<int,int> &y){
if(x.size()<y.size())
swap(x,y);
for(unordered_map<int,int>::iterator it=x.begin();it!=x.end();it++){
int a=it->first,b=it->second;
x[a]+=b;
}
y.clear();
}
int main(){
//freopen("r.in","r",stdin);
scanf("%d%d",&a,&b);
if(a==1){
for(int i=1;i<=b;i++)
scanf("%d",&v[i]);
int j=1;
for(int i=1;i<=b;){
j=i;
while(j<b&&v[i]==v[j+1])
j++;
for(int k=i;k<=j;k++){
int xx=fd(i),yy=fd(k);
if(xx!=yy)
p[xx]=yy;
}
l[fd(i)]=i;
r[fd(i)]=j;
t[fd(i)]=v[i];
i=j+1;
}
scanf("%d",&q);
while(q--){
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
t[fd(y)]=c;
while(1){
int g=fd(y),e=l[g],f=r[g],ok=0;
if(e>1&&t[fd(e-1)]==t[g]){
int yy=fd(e-1);
p[yy]=g;
l[g]=l[yy];
ok=1;
}
if(f<b&&t[fd(f+1)]==t[g]){
int yy=fd(f+1);
p[yy]=g;
r[g]=r[yy];
ok=1;
}
if(!ok)
break;
}
}
for(int i=1;i<=b;i++)
printf("%d ",t[fd(i)]);
return 0;
}
if(a*b<=10000){
for(int i=1;i<=a;i++)
for(int j=1;j<=b;j++)
scanf("%d",&c[id(i,j)]);
int q,ct=0;
scanf("%d",&q);
while(q--){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
qu.push((no){x,y});
while(!qu.empty()){
no x=qu.front();
qu.pop();
st[++tp]=x;
int e=x.x,f=x.y;
vi[id(e,f)]=1;
for(int i=0;i<4;i++){
int dx=e+tx[i],dy=f+ty[i];
if(dx<1||dx>a||dy<1||dy>b)
continue;
if(!vi[id(dx,dy)]&&c[id(dx,dy)]==c[id(e,f)]){
qu.push((no){dx,dy});
vi[id(dx,dy)]=1;
}
}
}
for(int i=1;i<=tp;i++){
c[id(st[i].x,st[i].y)]=z;
vi[id(st[i].x,st[i].y)]=0;
}
tp=0;
}
for(int i=1;i<=a;i++){
for(int j=1;j<=b;j++)
printf("%d ",c[id(i,j)]);
puts("");
}
return 0;
}
int ok=1;
for(int i=1;i<=a;i++)
for(int j=1;j<=b;j++){
scanf("%d",&c[id(i,j)]);
if(c[id(i,j)]!=1&&c[id(i,j)]!=0)
ok=0;
}
int q;
scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%d%d%d",&l[i],&r[i],&v[i]);
if(v[i]!=1&&v[i]!=0)
ok=0;
}
if(ok){
for(int i=1;i<=a;i++)
for(int j=1;j<=b;j++)
for(int d=0;d<4;d++){
int dx=i+tx[d],dy=j+ty[d];
if(dx<1||dx>a||dy<1||dy>b)
continue;
if(c[id(dx,dy)]==c[id(i,j)]){
ma[id(i,j)][id(dx,dy)]=1;
ma[id(dx,dy)][id(i,j)]=1;
}
t[id(i,j)]=c[id(i,j)];
}
for(int i=1;i<=a;i++)
for(int j=1;j<=b;j++)
if(!vi[id(i,j)]){
qu.push((no){i,j});
while(!qu.empty()){
no x=qu.front();
qu.pop();
int e=x.x,f=x.y;
vi[id(e,f)]=1;
for(int t=0;t<4;t++){
int dx=e+tx[t],dy=f+ty[t];
if(dx<1||dx>a||dy<1||dy>b)
continue;
if(!vi[id(dx,dy)]&&c[id(dx,dy)]==c[id(e,f)]){
int xx=fd(id(e,f)),yy=fd(id(dx,dy));
ma[xx].erase(yy);
ma[yy].erase(xx);
mg(ma[xx],ma[yy]);
p[xx]=yy;
qu.push((no){dx,dy});
vi[id(dx,dy)]=1;
}
}
}
}
for(int i=1;i<=q;i++){
int xx=fd(id(l[i],r[i]));
t[xx]=v[i];
for(unordered_map<int,int>::iterator it=ma[xx].begin();it!=ma[xx].end();it++){
int a=it->first,b=it->second;
int yy=fd(a);
ma[xx].erase(yy);
ma[yy].erase(xx);
p[yy]=xx;
mg(ma[xx],ma[yy]);
}
}
for(int i=1;i<=a;i++){
for(int j=1;j<=b;j++)
printf("%d ",t[fd(id(i,j))]);
puts("");
}
}
}
48分代码的局限性:我们在合并的时候需要合并和当前点颜色相同的点。
而在上一问的时候,我们不需要寻找当前点相邻的与当前点颜色相同的点。
考虑按照大小分块。(其他的都不太行)
如果一个连通块点数(<B),定义一个连通块为小的,否则定义为大的。
考虑我们对一个小的连通块进行操作。由于它的边界点数肯定是(<B)的,所以连出的边条数肯定(<B)。
考虑对每个点维护出边的边集和它连出的点在下文中链表的编号。
对于大点,还需维护每个点连出的相同颜色的点的链表和它邻接的大点编号。
使用并查集在根部存储颜色。
分四种情况:
1.改的点是小点,改完后当前点的相邻点没有和当前点相同的点。
遍历当前连通块的所有出边,如果是大点则更新大点的链表。
2.改的点是小点,改完后当前点的相邻点有和当前点相同的点。
遍历当前连通块的所有出边。
把所有出边指向的点的相邻边集启发式合并。
我们在删除的时候,只需要删除当前连通块点连向外面的出边。
还要更新和当前点直接相连的,和当前点颜色不同的大点的链表。
3.改的点是大点,改完后当前点的相邻点没有和当前点相同的点。
遍历当前连通块的所有出边,如果是大点则更新大点的链表。
总之此题思维难度不高但是细节较多。