洛谷P3295 萌萌哒 并查集 + ST表

又切一道紫题!!!

成功的(看了一吨题解之后),我A掉了第二道紫题。

好,我们仔细观察,发现这是一个排列组合问题。

有些限定条件,要相等的地方,我们就用并查集并起来。最后一查有多少个并查集,就有多少个位置可供自由选择。

所以答案就是10^(并查集数),去除前导0:*(9/10)

好,这样我们得到了一个O(mn)算法。

然后我们考虑优化:每个区间可能被合并多次。所以我们有两种选择:线段树/ST表。

考虑到这是ST表例题(???????),我们就来个ST表与并查集联动求解...

我们的ufs[i][j]代表在[i][2^j]这个区间内的情况。

然后每次合并的时候都往下合并两个j-1(也可以最后再一起下传标记)

实质上是开了logn个并查集,因为我发现find和merge都不跨层。

题外话:与RE战斗的艰辛历程

交了11次RE,实在是让人感受绝望啊。

两种方法全都RE,所幸刚才我写的时候都查出来错了。

那么先来看看第一种方法:

每次都跟线段树一样恰好标记完最少的节点,最后所有标记一起下传。

 1 #include <cstdio>
 2 using namespace std;
 3 const int N = 100010;
 4 const int mo = 1000000007;
 5 
 6 int ufs[N][30],n,m;
 7 int find(int x,int j)
 8 {
 9     if(ufs[x][j]!=x) ufs[x][j]=find(ufs[x][j],j);
10     return ufs[x][j];
11 }
12 void merge(int x,int y,int j)
13 {
14     ufs[find(x,j)][j]=find(y,j);///->!!这里调了一个错,之前是ufs[x][j]=......
15     return;
16 }
17 
18 
19 int main()
20 {
21     scanf("%d%d",&n,&m);
22     for(int j=0;j<=29;j++)
23     {
24         for(int i=1;i<=n;i++) ufs[i][j]=i;///->!
25     }
26     int a,b,c,d;
27     int md=0;
28     while((1<<md)<=n) md++;
29     md--;
30     for(int i=1;i<=m;i++)
31     {
32         scanf("%d%d%d%d",&a,&b,&c,&d);
33         for(int j=md;j>=0;j--)
34         {
35             if(a+(1<<j)-1<=b) merge(a,c,j),a+=(1<<j),c+=(1<<j);
36         }
37     }
38     ///
39     for(int j=md;j>=1;j--)///这里,RE的罪魁祸首!我之前写的0,结果传进去个j=-1直接挂
40     {
41         for(int i=1;i+(1<<j)-1<=n;i++)
42         {
43             merge(i,find(i,j),j-1);
44             merge(i+(1<<(j-1)),find(i,j)+(1<<(j-1)),j-1);
45         }
46     }
47     ///
48     long long ans=9;
49     bool q=false;
50     for(int i=1;i<=n;i++)
51     {
52         if(find(i,0)==i)
53         {
54             if(q) ans=(ans*10)%mo;
55             q=1;
56         }
57     }
58     //for(int i=1;i<=n;i++) printf("%d ",find(i,0));
59     printf("%lld",ans);
60     return 0;
61 }
AC代码,跑的比下面快一些

第二种思路:

每次都传到底。如果已经在一起就不往下推了。

 1 #include <cstdio>
 2 using namespace std;
 3 const int N = 100010;
 4 const int mo = 1000000007;
 5 
 6 int ufs[N][30],n,m;
 7 int ffind(int x,int j)
 8 {
 9     //if(ufs[x][j]!=x) ufs[x][j]=find(ufs[x][j],j);
10     //return ufs[x][j];
11     if(j<0) return 0;
12     int ans=x,k;
13     while(ufs[ans][j]!=ans) ans=ufs[ans][j];
14     while(ufs[x][j]!=x)
15     {
16         k=ufs[x][j];
17         ufs[x][j]=ans;
18         x=k;
19     }
20     return ans;
21 }
22 void mmerge(int x,int y,int j)
23 {
24     if(j<0) return;///这里控制情况,AC
25     if(ffind(x,j)==ffind(y,j)) return;
26     ufs[ffind(x,j)][j]=ffind(y,j);///->!!
27     mmerge(x,y,j-1),mmerge(x+(1<<(j-1)),y+(1<<(j-1)),j-1);///这里RE!会传入j=-1
28     return;
29 }
30 
31 
32 int main()
33 {
34     scanf("%d%d",&n,&m);
35     for(int j=0;j<=29;j++)
36     {
37         for(int i=1;i<=n;i++) ufs[i][j]=i;///->!
38     }
39     int a,b,c,d;
40     int md=0;
41     while((1<<md)<=n) md++;
42     md--;
43     for(int i=1;i<=m;i++)
44     {
45         scanf("%d%d%d%d",&a,&b,&c,&d);
46         for(int j=md;j>=0;j--)
47         {
48             if(a+(1<<j)-1<=b) mmerge(a,c,j),a+=(1<<j),c+=(1<<j);
49         }
50     }
51     /**
52  
53     */
54     long long ans=9;
55     bool q=false;
56     for(int i=1;i<=n;i++)
57     {
58         if(ffind(i,0)==i)
59         {
60             if(q) ans=(ans*10)%mo;
61             q=1;
62         }
63     }
64     //for(int i=1;i<=n;i++) printf("%d ",find(i,0));
65     printf("%lld",ans);
66     return 0;
67 }
AC代码

结论:函数里最好写上特判违法情况,保险。

原文地址:https://www.cnblogs.com/huyufeifei/p/8718999.html