P3740 [HAOI2014]贴海报
并查集
分析
题意:给出一个长度为
由于只需要所有操作结束后序列的信息,该问题可以离线求解。
性质:时间较晚的染色操作覆盖时间较早的染色操作。
反转时间轴,从最后一次操作开始染色。元素第一次被染上的颜色就是所有操作结束后的颜色,一旦被染色就可以不再考虑。
实现
因为元素只会从未染色的变成已染色的,可以使用并查集维护序列。具体地,维护序列的连通性。
使用
每个极长连通块与其左边的第一个元素形成一个集合,集合的根节点为连通块左边第一个元素(不在连通块内)。 这个元素显然是未染色的,否则就不符合极长连通块的定义。如此一来,通过询问元素在并查集中的祖先就可以快速找到该元素左边第一个未染色的元素(如果元素未染色则找到的是元素本身)。可以发现一个元素未被染色当且仅当
删除操作只需要让被删除的元素不再被遍历到,如果想要删除元素
代码总体时间复杂度
注意:如果在开始染色时没有调用并查集初始化,可能会修改到已经染色的元素。
#include<bits/stdc++.h>
using namespace std;
struct Post{int l,r;}s[1001];
int n,m,ans,f[10000001],c[10000001];
bool on[1001];
int ask(int x){return x==f[x] ? x:f[x]=ask(f[x]);}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++) scanf("%d%d",&s[i].l,&s[i].r);
for(int i=m;i>=1;i--)
for(int j=ask(s[i].r);j>=s[i].l;j=ask(j-1))//调用并查集初始化
c[j]=i,f[j]=j-1;
for(int i=1;i<=n;i++) if(c[i]&&!on[c[i]]) on[c[i]]=1,++ans;
printf("%d\n",ans);
return 0;
}
递归实现的路径压缩并查集会开爆栈,而使用循环实现的路径压缩并查集可以通过本题。
代码总体时间复杂度
#include<bits/stdc++.h>
using namespace std;
struct Post{int l,r;}s[1001];
int n,m,ans,top,f[10000001],c[10000001],v[10000001];
bool on[1001];
int ask(int x){
while(f[x]!=x) v[++top]=x,x=f[x];
while(top) f[v[top]]=x,--top;//路径压缩
return x;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++) scanf("%d%d",&s[i].l,&s[i].r);
for(int i=m;i>=1;i--)
for(int j=ask(s[i].r);j>=s[i].l;j=ask(j-1))//调用并查集初始化
c[j]=i,f[j]=j-1;
for(int i=1;i<=n;i++) if(c[i]&&!on[c[i]]) on[c[i]]=1,++ans;
printf("%d\n",ans);
return 0;
}
离散化
将序列离散化后再染色可以通过此题。事实上离散化后暴力染色的做法时间复杂度为
只对区间的左右端点离散化无法通过此题。 这会导致序列原有的信息丢失。
在离散化时额外加入区间右端点右边的元素和区间左端点左边的元素即可使染色操作在正确位置中止。实际上从右到左染色时加入左端点左边的元素已经足够保证正确性,从左到右则相反。
Code
时间复杂度