[SCOI2016]萌萌哒 解题报告

[SCOI2016]萌萌哒

题意

有一个长度为 (n) 的大数,

(m) 个形如 l1,r1,l2,r2的限制, 表示区间 ([l1,r1])([l2,r2]) 完全相等,

求满足这些限制的数的个数, 不能含有前导零.

((1 le n,m le 10^5))


思路

暴力 : 直接 (O(nm)) 把相等的数合成一个并查集, 最后若并查集的数量为 (k), 则答案为 (9 imes 10^{k-1}). (不能有前导零).


考虑优化.

首先想到线段树优化连边, 但是发现用线段树的话, 无法确定区间的对应关系.


为了连边之后依旧能确定区间的对应关系, 我们需要对每一个点都有一个以它为起点的存储结构.

所以, 我们可以弄一个类似于 ST表 的东西, 但理解方式类似于 线段树.

把整个序列分成 (log n) 层, 每一层之间进行连边.

在给定了 l1,r1,l2,r2 时, 用类似于ST表查询最值时的操作, 把 ([l1,r1])([l2,r2]) 分别分成两个长度为 (2) 的若干次方的区间, 然后在这几个区间之间连边.

在所有边都连完后, 从上往下一层一层地把连边下传, 反映在代码中其实就是并查集的合并.


需要注意的是, 在下传的时候, 设当前层的两个连接区间为 (A,B), 我们还需要考虑 (A)(B) 的子区间, 即下一层的四个区间 (A_1,A_2)(B_1,B_2) 的连接情况,

(A_1,A_2) 是相连的, 就把 (fa[B_1],fa[B_2]) 设为 (fa[A_1]),

否则, 就把 (fa[A_1],fa[A_2]) 设为 (fa[B_1]).


代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int _=1e5+7;
const int L=20;
const ll mod=1e9+7;
int n,m,fa[_][L+7],Log[_];
bool vis[_];
ll ans;
void init(){
  cin>>n>>m;
  int t=0;
  for(int i=1;i<=n;i++){
    if(i==1<<(t+1)) t++;
    Log[i]=t;
    for(int k=0;k<=L;k++)
      fa[i][k]=i;
  }
}
int find(int i,int k){ return fa[i][k]==i ?i :fa[i][k]=find(fa[i][k],k); }
void run(){
  int l1,r1,l2,r2;
  int maxn=0;
  for(int i=1;i<=m;i++){
    scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
    maxn=max(maxn,max(r1,r2));
    int t=Log[r1-l1+1];
    int f1=find(l1,t),f2=find(l2,t);
    fa[f1][t]=f2;
    f1=find(r1-(1<<t)+1,t),f2=find(r2-(1<<t)+1,t);
    fa[f1][t]=f2;
  }

  for(int k=L;k>=1;k--){
    for(int i=1;i+(1<<k)-1<=n;i++){
      int t=find(i,k),t1=find(i,k-1),t2=find(i+(1<<(k-1)),k-1);
      if(t1==t2){ fa[find(t,k-1)][k-1]=fa[find(t+(1<<(k-1)),k-1)][k-1]=t1; }
      else{ fa[t1][k-1]=find(t,k-1); fa[t2][k-1]=find(t+(1<<(k-1)),k-1); }
	
    }
  }
  int j=find(1,0);
  ans=9; 
  for(int i=2;i<=n;i++)
    if(i!=j&&find(i,0)==i)
      ans=ans*10%mod;
}
int main(){
#ifndef ONLINE_JUDGE
  freopen("x.in","r",stdin);
#endif
  init();
  run();
  printf("%lld
",ans);
  return 0;
}
原文地址:https://www.cnblogs.com/BruceW/p/12194438.html