一个显然的暴力是用并查集记录哪些位之间是相等的。但是这样需要连nm条边,而实际上至多只有n条边是有用的,冗余过多。
于是考虑优化。使用类似st表的东西,f[i][j]表示i~i+2^j-1与f[i][j]~f[i][j]+2^j-1连接起来了,也就是把这一大段看成一个点所建立的并查集。那么每个限制只要拆成两段就可以了。最后查询的时候,需要把信息下传,即f[i][j]下传到f[i][j-1]和f[i+2^(j-1)][j-1],表示这两段各自分别对应。于是复杂度变成了O(nlognαn)。这个做法能优化复杂度的关键在于每次下传过程中都去除了一些冗余边使其不再会下传了。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } #define N 100010 #define P 1000000007 int n,m,fa[N][18],lg2[N],ans=0; int find(int x,int j){return fa[x][j]==x?x:fa[x][j]=find(fa[x][j],j);} void merge(int x,int y,int k){fa[find(x,k)][k]=find(y,k);} int main() { #ifndef ONLINE_JUDGE freopen("bzoj4569.in","r",stdin); freopen("bzoj4569.out","w",stdout); const char LL[]="%I64d"; #else const char LL[]="%lld"; #endif n=read(),m=read(); for (int i=1;i<=n;i++) for (int j=0;j<=17;j++) fa[i][j]=i; lg2[1]=0; for (int i=1;i<=n;i++) { lg2[i]=lg2[i-1]; if ((2<<lg2[i])<=i) lg2[i]++; } for (int i=1;i<=m;i++) { int l1=read(),r1=read(),l2=read(),r2=read(); merge(l1,l2,lg2[r1-l1+1]); merge(r1-(1<<lg2[r1-l1+1])+1,r2-(1<<lg2[r1-l1+1])+1,lg2[r1-l1+1]); } for (int j=17;j>=1;j--) for (int i=1;i<=n;i++) if (fa[i][j]!=i) merge(i,fa[i][j],j-1),merge(i+(1<<j-1),fa[i][j]+(1<<j-1),j-1); for (int i=1;i<=n;i++) if (find(i,0)==i) ans++; int t=9; for (int i=2;i<=ans;i++) t=10ll*t%P; cout<<t; return 0; }