题面:
n
,
m
≤
1
0
5
n,m\le10^5
n,m≤105
题目分析:
容易想到的暴力做法是把每次的区间要求[l,r]拆分成r-l+1个单点要求,并查集维护单点要求,最后扫一遍查询。
复杂度是
O
(
n
m
α
)
O(nm\alpha)
O(nmα)的,但是由于出题人太过良心,这样你就能得到80分。
把要求每次拆分很显然要T,我们考虑优化每次的要求,其时间就浪费在重复要求已经相等的点对上。
怎么办呢,其实只要再稍微一想就能想到ST表维护了。
把一个整段区间拆成两段2的幂,
[
l
1
,
r
1
]
[l_1,r_1]
[l1,r1]与
[
l
2
,
r
2
]
[l_2,r_2]
[l2,r2]相等就相当于
[
l
1
,
l
1
+
2
k
−
1
]
[l_1,l_1+2^k-1]
[l1,l1+2k−1]与
[
l
2
,
l
2
+
2
k
−
1
]
[l_2,l_2+2^k-1]
[l2,l2+2k−1]相等,并且
[
r
1
−
2
k
+
1
,
r
1
]
[r_1-2^k+1,r_1]
[r1−2k+1,r1]与
[
r
2
−
2
k
+
1
,
r
2
]
[r_2-2^k+1,r_2]
[r2−2k+1,r2]相等。
这样把划分区间,就是logn层,每层的区间长度为20,21,…,每层n段。每次要求就把对应层的区间用并查集合并(二维并查集)。
最后再由上至下挨个合并每层(相当于上一层的标记下放),最后看第0层有多少个连通块就行了。
或者直接在一开始合并的时候就下放,碰到下放的两区间已经在一块的时候就停止,最后直接查询第0层。
每一层最多合并n次,logn层,所以总的复杂度是 O ( ( n l o g n + m ) α ) O((nlogn+m)\alpha) O((nlogn+m)α)。
Code:
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
const int mod = 1e9+7;
int n,m,lg[maxn],f[18][maxn];
int find(int k,int x){return f[k][x]==x?x:f[k][x]=find(k,f[k][x]);}
void merge(int k,int l,int r){
if((l=find(k,l))!=(r=find(k,r))) f[k][l]=r;
}
vector<int>q[maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
for(int i=lg[n];i>=0;i--) for(int j=1;j<=n;j++) f[i][j]=j;
int l1,r1,l2,r2,k;
while(m--){
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
k=lg[r1-l1+1];
merge(k,l1,l2),merge(k,r1-(1<<k)+1,r2-(1<<k)+1);
}
for(int i=lg[n];i>=1;i--){
for(int j=1;j<=n;j++) q[j].clear();
for(int j=1;j<=n;j++){
printf("%d\n",find(i,j));
q[find(i,j)].push_back(j);
}
for(int j=1;j<=n;j++) if(q[j].size()>1)
for(int k=1;k<q[j].size();k++)
merge(i-1,q[j][0],q[j][k]),merge(i-1,q[j][0]+(1<<(i-1)),q[j][k]+(1<<(i-1)));
}
int s=0,ans=9;
for(int i=1;i<=n;i++) if(f[0][i]==i) s++;
for(int i=1;i<s;i++) ans=ans*10%mod;
printf("%d\n",ans);
}