P3295 [SCOI2016] 萌萌哒
好题目啊!
看到题目,思考一下发现我们要找出所有不同的位置的个数,假设这个数字为 \(ans\),那么答案就是 \(10^{ans-1}\times 9\)。
为什么要减一?因为题目中说这是一个十进制大数,所以第一位不能是0,有 \(9\) 种选择。
按照这个思路,我们可以用并查集来维护,朴素代码如下。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,mod=1e9+7;
int kasumi(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod,b>>=1;
}
return res;
}
int n,m,f[N],vis[N],ans;
int find(int x)
{
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
void merge(int x,int y)
{
if(find(x)==find(y))return;
f[f[x]]=f[y];
return;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1,l1,r1,l2,r2;i<=m;i++)
{
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
for(int j=1;j<=r1-l1+1;j++)merge(l1+j-1,l2+j-1);
}
for(int i=1;i<=n;i++)ans+=(!vis[find(i)]),vis[f[i]]=1;
printf("%d",9ll*kasumi(10,ans-1)%mod);
return 0;
}
但这只能拿到 \(30\) 分,怎么进一步优化呢?
事实上,瓶颈在于每个限制条件,我们都要为对应的每一位一一在并查集中合并,效率很低。
所以可以把\(l1\sim r1\)、\(l2\sim r2\) 切成一块块,把每一块分别合并就好了。
因为最终要统计每一位的相同情况,所以分块不太行,就可以采用倍增了。
最后还要把大块上的合并传到小块上面,代码如下。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,mod=1e9+7;
int kasumi(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod,b>>=1;
}
return res;
}
int n,m,f[N][25],vis[N],ans;
int find(int x,int s)
{
if(f[x][s]==x)return x;
return f[x][s]=find(f[x][s],s);
}
void merge(int x,int y,int s)
{
if(find(x,s)==find(y,s))return;
f[f[x][s]][s]=f[y][s];
return;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)for(int j=0;j<=20;j++)f[i][j]=i;
for(int i=1,l1,r1,l2,r2;i<=m;i++)
{
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
for(int j=20;j>=0;j--)if(l1+(1<<j)-1<=r1)merge(l1,l2,j),l1+=(1<<j),l2+=(1<<j);
}
for(int j=19;j>0;j--)for(int i=1,p;i+(1<<j)-1<=n;i++)p=find(i,j),merge(i,p,j-1),merge(i+(1<<(j-1)),p+(1<<(j-1)),j-1);;
for(int i=1;i<=n;i++)ans+=(!vis[find(i,0)]),vis[f[i][0]]=1;
printf("%d",9ll*kasumi(10,ans-1)%mod);
return 0;
}

浙公网安备 33010602011771号