P3295 SCOI2016 萌萌哒
P3295 SCOI2016 萌萌哒
有趣的并查集+倍增 trick。
思路
考虑将必须填同一个数的位置连一条边,最后若出现了 \(k\) 个连通块,那答案就是 \(9\times 10^{k-1}\)(首位不为 \(0\))。
我们用并查集暴力连接,时间复杂度 \(O(nm)\),显然是无法接受的。
有没有办法一次性可以连很多块呢?
用倍增来连接一长段,将区间 \([l_1,r_1]\) 与 \([l_2,r_2]\) 分为 \(\log (r-l)\) 段,对应每段之间都用并查集连接,一共连接 \(\log (r-l)\) 次,表示的含义为这两段完全相同。
由于查询需要落在点上,设 \(f(p,k)\) 为 \(p\) 向后 \(2^k\) 并查集的根,我们将段 \((f(p,k),k-1)\) 与 \((p,k-1)\) 连接、\((f(p,k)+2^{k-1},k-1)\) 与 \((p+2^{k-1},k-1)\) 连接,这样就将一段下传到点上。
最后统计连通块个数即可。
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
const int maxn=1e5+5;
int n,m;
int lg[maxn];
struct DSU
{
int f[maxn];
inline void init(int n){for(int i=1;i<=n;i++) f[i]=i;}
inline int fr(int u){return u==f[u]?u:f[u]=fr(f[u]);}
inline void merge(int u,int v){u=fr(u),v=fr(v);if(u==v) return ;f[u]=v;}
}F[20];
bool vis[maxn];
inline ll ksm(ll x,ll y)
{
ll sum=1;
for(;y;y/=2,x=x*x%mod) if(y&1) sum=sum*x%mod;
return sum;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
for(int i=0;i<=lg[n];i++) F[i].init(n);
for(int i=1;i<=m;i++)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
int len=r1-l1+1;
for(int k=0;len;len>>=1,k++)
{
if(len&1)
{
int p1=l1+((len>>1)<<(k+1)),p2=p1+(l2-l1);
F[k].merge(p1,p2);
}
}
}
for(int i=lg[n];i;i--)
{
for(int j=1;j<=n;j++)
{
int f=F[i].fr(j);
F[i-1].merge(f,j);
F[i-1].merge(f+(1<<(i-1)),j+(1<<(i-1)));
}
}
int num=0;
for(int i=1;i<=n;i++) if(!vis[F[0].fr(i)]) vis[F[0].fr(i)]=1,num++;
printf("%lld\n",9*ksm(10,num-1)%mod);
}

浙公网安备 33010602011771号