题目链接:萌萌哒

  我先不吐槽题目名……这道题的并查集好像我们考过……既然那道题我没写就来把这道题写了吧(雾

  这道题由于合并操作只有\(m\)次,那么很显然的一个想法就是把建一棵线段树类似物,然后每次在上面分裂区间。但是由于只有区间长度相同的才能用并查集直接维护,所以时间复杂度是\(O(m \log ^2n)\)。注意这里和下文都没有考虑并查集复杂度。

  这样做的话我们把原序列用\(O(n)\)个区间表示了,但是每次操作是\(O(\log^2 n)\)的。如果我们找出了更多的区间,也许就可以把单词操作的复杂度降一点。事实上,我们只需要像\(st\)表那样倍增地抠区间,那么单次操作的复杂度就只有\(O(1)\)了。每次操作时分裂成两个区间,分别在并查集中并到一起就可以了。

  最后不要忘记了把相等关系给下传。统计答案的时候注意不能有前导零。

  下面贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define maxn 100010
#define mod 1000000007 using namespace std;
typedef long long llg; int f[17][maxn],siz[17][maxn];
int mi[17],cnt,n,m,lo[maxn];
llg ans=1; int getint(){
int w=0;bool q=0;
char c=getchar();
while((c>'9'||c<'0')&&c!='-') c=getchar();
if(c=='-') c=getchar(),q=1;
while(c>='0'&&c<='9') w=w*10+c-'0',c=getchar();
return q?-w:w;
} int find(int i,int x){return f[i][x]==x?x:f[i][x]=find(i,f[i][x]);}
void merge(int i,int x,int y){
int a=find(i,x),b=find(i,y);
if(a!=b){
if(siz[i][a]>siz[i][b]) swap(a,b);
f[i][a]=b; siz[i][b]+=siz[i][a];
}
} int main(){
File("a");
n=getint(); m=getint(); mi[0]=1;
for(int i=1;i<=16;i++) lo[(mi[i]=mi[i-1]<<1)+1]=i;
for(int i=1;i<=n;i++) lo[i]=max(lo[i],lo[i-1]);
for(int i=0;i<=lo[n];i++)
for(int j=1;j<=n-mi[i]+1;j++)
f[i][j]=j,siz[i][j]=1;
while(m--){
int l1,r1,l2,r2,t;
l1=getint(),r1=getint();
l2=getint(),r2=getint();
t=lo[r1-l1+1]; merge(t,l1,l2);
merge(t,r1-mi[t]+1,r2-mi[t]+1);
}
for(int i=lo[n];i>0;i--)
for(int j=1;j<=n-mi[i]+1;j++)
if(find(i,j)!=j){
merge(i-1,j,find(i,j));
merge(i-1,j+mi[i-1],find(i,j)+mi[i-1]);
}
for(int i=1;i<=n;i++) cnt+=(f[0][i]==i);
for(int i=1;i<cnt;i++) ans*=10,ans%=mod;
if(cnt) ans*=9,ans%=mod;
printf("%lld",ans);
return 0;
}
05-11 17:15