又切一道紫题!!!
成功的(看了一吨题解之后),我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 }
第二种思路:
每次都传到底。如果已经在一起就不往下推了。
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 }
结论:函数里最好写上特判违法情况,保险。